技术博客:www.zhenganwen.topphp
暨上文介绍了
PlantUML
以后,谨以此文记录学习《图解设计模式》过程当中的心得体会,欢迎留言一块儿交流想法~html
Adapt to Design Pattern——适应设计模式java
Left to subclass——交给子类node
Generating Instance——生成实例python
Consider Individualy——分开考虑mysql
Consistency——一致性git
Access Data Structure——访问数据结构算法
Simplify——简单化sql
Manage Status——管理状态编程
Avoid wasting
Represent with Class——用类来表现
因为每一个人的行文风格不一样,所以表情达意的方式也不一样,在本文中有着以下约定:
Client
类都表示业务类,即脱离于设计模式以外的,将设计模式应用于业务代码的测试类。IDEA
插件PlantUML
所画,可参考《碰见PantUML~》一文一般,优良的代码设计须要遵循如下原则
每一个类的存在应该都只是为了知足一个特定的需求,例如Collection
类中的方法应该都是为了维护内部元素的结构组织而存在,而应该将如何遍历Collection
中元素的职责交给Iterator
。
单一职责保证专业的事交给专业的人来作,这样每一个类发生修改的缘由只会有一个(由于每一个类的责任只有一个),这样就保证了后续如有需求变动只会致使负责解决该需求的类发生改变,而其余的类均不会受到影响。改变越少,系统发生BUG的概率就会越小。
系统要对修改关闭,对扩展开放。代码设计要尽可能避免对现有代码的修改,由于一旦修改一处就可能致使依赖该类的其余类发生改变,一旦改变,就有可能引入新的潜在的BUG。若是需求变动,代码设计应该经过新增类(多为实现类)的方式来知足新的需求,而客户端代码(依赖该类的其余类)应该无需修改或只需少许修改。
里氏代换原则依托于OOP的多态性。在运行时,客户端依赖的对象可被其余“同源”的(有相同的父类或接口)对象替换而客户端的调用逻辑不受任何影响,这要求咱们在声明对象的外观类型(声明类型)时尽可能选择高层次一些的类(类的层次结构)。
接口隔离原则要求咱们将接口方法按照接口功能分开定义在不一样的接口中,例如createItertor()
应该定义在Iterable
中,fly()
应该定义在Flyable
之中,这样可以减轻实现类的负担,避免实现类被捆绑着要求实现没必要要的接口方法。
同时,Java8以后,接口方法若是有通用实现应该定义为default
依赖倒置原则要求咱们尽可能消除点对点的“强”依赖,而应该使二者依赖抽象,例如Controller
依赖AbstractService
中的抽象方法进行声明式编程,XxxServiceImpl
则对AbstractService
的抽象方法进行实现,这样就实现了控制器和具体业务处理类之间的解耦,一旦后续业务变动,咱们只须要新增一个XxxServiceImpl2
并借助多态就可以轻松实现业务处理的切换。
本章将以Iterator
模式和Adaptor
模式两个较为简单的做为设计模式的入门,切身体会设计模式存在的价值和软件开发中应遵循的一些原则。
Iterator
中译“迭代器”,起逐个访问集合中的元素的做用。在数据结构中组合元素的方式有不少,如数组、连表、哈希表、二叉树等,根据集合的不一样的组织形式,咱们遍历访问集合元素的方式也是不同的。这时咱们的业务代码中的访问逻辑(即遍历到当前元素时须要干什么)和遍历逻辑(须要知道集合内部结构)是耦合在一块儿的,一旦集合换一种组织形式,那么咱们的业务代码也须要跟着改变。
例如,以下书架BookShelf
经过数组的形式组织了一些书Book
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private String name;
}
public class BookShelfWithArr {
private final Book[] books;
private final int size;
private int index;
public BookShelfWithArr(int size) {
this.size = size;
this.books = new Book[size];
this.index = 0;
}
public void put(Book book) {
if (book == null) {
throw new IllegalArgumentException("book can't be null");
}
if (index == size) {
throw new RuntimeException("the bookshelf is full");
}
books[index++] = book;
}
public Book get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("index should be equal or big than 0 but less than " + size);
}
return books[index];
}
public int size() {
return this.size;
}
}
复制代码
若是不使用Iterator
模式,那么你的业务代码可能以下所示:
public class Client {
public static void main(String[] args) {
BookShelfWithArr bookShelf = new BookShelfWithArr(4);
bookShelf.put(new Book("Java"));
bookShelf.put(new Book("C"));
bookShelf.put(new Book("php"));
bookShelf.put(new Book("python"));
for (int i = 0; i < bookShelf.size(); i++) {
System.out.println(bookShelf.get(i));
}
}
}
复制代码
这是咱们初学Java集合章节时司空见惯的代码。如今咱们来考虑一个问题,假设这个书架被打形成可伸缩的,便可以根据咱们所放书籍数量而变大变小,此时咱们该怎么办?
BookShelf
好说,根据可扩容的特性,咱们能够应用ArrayList
来代替数组
@Data
public class BookShelf {
private ArrayList<Book> bookList;
public BookShelf() {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList;
}
}
复制代码
如此的话,咱们的遍历访问逻辑就要作出相应调整
BookShelf bookShelf2 = new BookShelf();
bookShelf2.getBookList().add(new Book("Java"));
bookShelf2.getBookList().add(new Book("C"));
bookShelf2.getBookList().add(new Book("php"));
bookShelf2.getBookList().add(new Book("python"));
for (int i = 0; i < bookShelf2.getBookList().size(); i++) {
System.out.println(bookShelf2.getBookList().get(i));
}
复制代码
这里一旦集合改变组织元素的方式,任何其余存在遍历该集合对象的代码(相对于该集合来讲,这些代码称为客户端代码)都须要跟着改变。意味着,客户端代码和集合是紧耦合的,客户端代码不该该关心集合内部是如何组织元素的,而只应该关心遍历该集合拿到元素以后应该作什么。
因而咱们用Iterator
模式来改造一下
首先不管集合如何组织元素,它都应该是可遍历的,所以须要抽象出两个方法:
boolean hasNext
——集合中是否还有未遍历的元素E next()
——取出下一个未遍历的元素public interface Iterator<E> {
boolean hasNext();
E next();
}
复制代码
集合应该只关注如何组织元素,所以应该将上述遍历逻辑交由他人Iterator
来作
public interface Iterable<E> {
Iterator<E> iterator();
}
复制代码
经过调用BookShelf
的iterator
方法咱们能够获取BookShelf
的迭代器,经过使用该迭代器的方法能够实现对BookShelf
中元素的遍历访问:
public class BookShelfIterator implements Iterator<Book> {
private int index;
private BookShelf bookShelf;
public BookShelfIterator(BookShelf bookShelf) {
this.index = 0;
this.bookShelf = bookShelf;
}
@Override
public boolean hasNext() {
return index < bookShelf.getBookList().size();
}
@Override
public Book next() {
if (!hasNext()) {
throw new RuntimeException("you have arrived the end")
}
return bookShelf.getBookList().get(index++);
}
}
@Data
public class BookShelf implements Iterable<Book> {
private ArrayList<Book> bookList;
public BookShelf() {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList;
}
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
复制代码
客户端代码:
public class IteratorClient {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf();
bookShelf.getBookList().add(new Book("Java"));
bookShelf.getBookList().add(new Book("C"));
bookShelf.getBookList().add(new Book("php"));
bookShelf.getBookList().add(new Book("python"));
Iterator<Book> iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
复制代码
如此,若是BookShelf
换用Map
来组织元素,咱们只需新增一个BookShelfMapIterator
便可,而客户端代码无需任何改动:
@Data
public class MapBookShelf implements Iterable{
/** * book's name -> book */
private Map<String, Book> bookMap = new HashMap<>();
@Override
public Iterator iterator() {
return new MapBookShelfIterator(this);
}
}
复制代码
public class MapBookShelfIterator implements Iterator {
private final MapBookShelf mapBookShelf;
private final Object[] keys;
private int index;
public MapBookShelfIterator(MapBookShelf mapBookShelf) {
this.mapBookShelf = mapBookShelf;
this.keys = mapBookShelf.getBookMap().keySet().toArray();
this.index = 0;
}
@Override
public boolean hasNext() {
return index < keys.length;
}
@Override
public Object next() {
if (!hasNext()) {
throw new RuntimeException("you have arrived the end");
}
return mapBookShelf.getBookMap().get(keys[index++]);
}
}
复制代码
MapBookShelf bookShelf = new MapBookShelf();
bookShelf.getBookMap().put("Java", new Book("Java"));
bookShelf.getBookMap().put("C",new Book("C"));
bookShelf.getBookMap().put("PHP",new Book("php"));
bookShelf.getBookMap().put("Python", new Book("python"));
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
复制代码
虽然上述客户端代码也发生了改变,如getBookMap
、put
(这些改变能够经过抽象出一个AbstractBookShelf
来避免),可是遍历访问逻辑没变,即首先取得集合的Iterator
实例,而后调用接口方法hasNext
和next
进行遍历,hasNext
是对遍历界限的一个控制,其自己不作任何事,仅判断当前位置是否有元素;而next
则作两件事:返回当前位置上的元素,将遍历指针后移。
完整的Iterator
模式可表述以下
其中Iterable
和Iterator
对应,ConcreteIterable
和ConcreteIterator
对应,客户端仅知道Iterator
和Iterable
中的3个方法,可用此实现集合的遍历而无论集合内部组织形式,不一样的集合实例则将其对应的ConcreteIterator
实现隐藏在了createIterator
方法中
Iterator
,迭代器,专门负责迭代集合,符合单一职责
boolean hasNext()
E next()
Iterable
,集合须要实现该接口,将遍历责任委托给具体的迭代器实例
Iterator createIterator()
Collection
,集合ConcreteIterator
,具体的迭代器,和具体的集合实例之间是相互依赖的关系Adapter
是为了将已有的实现适应不一样的接口而存在。生活中的典型例子是,为了能使两个插销插头查到三个插孔的插座上,一般会在二者之间加上一个插口转换器,这个转换器承担的角色就是本设计模式的用意。
为了使已有的方法适应新的接口(例如已有方法健壮没有毛病,针对相同功能的新接口咱们又不像写重复代码),咱们一般会编写一个Adapter
,它仅仅起着一个转换器的做用。
Adapter
能够经过继承和委托两种方式实现
例如,系统中遗留着他人已写好的字符串打印类Banner
public class Banner {
public void printWithBracket(String s) {
System.out.println("(" + s + ")");
}
public void printWithStar(String s) {
System.out.println("*" + s + "*");
}
}
复制代码
如今你正在对系统迭代,须要为新的接口Print
编写实现类
public interface Print {
void printWeak(String s);
void printStrengthen(String s);
}
复制代码
而你的实现逻辑和Banner
中的两个已有方法不谋而和,因而你可经过继承旧类、实现新接口的方式,既能避免重复代码的编写,又对新接口有所交代
public class PrintBanner extends Banner implements Print {
@Override
public void printWeak(String s) {
this.printWithBracket(s);
}
@Override
public void printStrengthen(String s) {
this.printWithStar(s);
}
}
复制代码
此种方式的缺点是,若目标类(这里指
你还能够经过聚合的方式,将实现逻辑委托给旧类:
public class CustomPrint implements Print {
private Banner banner;
public CustomPrint(Banner banner) {
this.banner = banner;
}
@Override
public void printWeak(String s) {
banner.printWithBracket(s);
}
@Override
public void printStrengthen(String s) {
banner.printWithStar(s);
}
}
复制代码
如下是两种方式的类图
其中方式1受目标类必须是接口的限制。
Adaptee
,被适配方,一般为系统中的旧类,且类中存在对某功能A的实现Target
,目标类,一般为新添加到系统中的类,包含须要实现某功能A的抽象方法Adapter
,适配类,做为被适配方和目标类之间的桥梁,经过继承或聚合的方式实现代码复用模板方法模式属于特殊的“声明式”编程,即经过抽象定义一个业务处理逻辑中各步骤的前后执行顺序,但对于各步骤的具体实现并不关心,交由运行时实际的子类对象受理。这也充分利用了OOP的多态性
例如,现有一个订单业务类OrderService
以下:
public class OrderService {
public void makeOrder() {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification() {
System.out.println("安全校验");
}
public void reduceStock() {
System.out.println("去MySQL减库存");
}
public void reduceBalance() {
System.out.println("去MySQL减余额");
}
public void noticeDelivery() {
System.out.println("通知发货");
}
}
复制代码
客户端代码以下
public class Client {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.makeOrder();
}
}
复制代码
上述代码逻辑清晰,看起来没什么问题。但假设如今要作秒杀,须要将库存、余额等信息作一个缓存,那你就须要在原有的OrderService
上作修改了。这违反了“开闭原则”(应对修改关闭而对扩展开放)。
此时咱们就须要将业务步骤抽象出来,具体的实现交由特定的子类去作,以知足不一样的业务场景:
public abstract class AbstractOrderService {
public void makeOrder() {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification(){
System.out.println("安全校验");
}
public abstract void reduceStock() ;
public abstract void reduceBalance();
public void noticeDelivery(){
System.out.println("通知发货");
}
}
复制代码
此时若须要缓存支持,只需新增一个实现类便可
public class OrderServiceWithCache extends AbstractOrderService {
@Override
public void reduceStock() {
System.out.println("从缓存中减库存");
}
@Override
public void reduceBalance() {
System.out.println("从缓存中减余额");
}
}
复制代码
客户端代码只需切换具体的实现类:
public class Client {
public static void main(String[] args) {
AbstractOrderService orderService = new OrderServiceWithCache(); //使用缓存
orderService.makeOrder();
orderService = new OrderService(); // 切换到MySQL
orderService.makeOrder();
}
}
复制代码
模板方法模式就是在抽象类中进行声明式编程(使用抽象方法的形式强调该业务的完成应该执行哪些步骤以及这些步骤的执行顺序,而不关注每一个步骤具体是如何实现的),而将具体业务步骤的实现交由子类(运行时经过多态)完成。
在客户端看来,虽然只是切换了一会儿类实例,但好像被切换的实例实现的具体步骤就被注入到总体地业务处理之中同样。
而且对于通用的步骤,如上述的safeVerification
和noticeDelivery
,可能它们的处理逻辑是固定的,这时能够将它们提取到父类中,实现复用。
SuperClass & Template Method
,模板方法一般声明在抽象类或接口中,调用本类的抽象方法(固然也能够是包含通用逻辑的非抽象方法)完成特定的业务逻辑。Concrete SubClass & Realization
,子类只需实现相应的抽象方法就能够将具体的功能步骤注入到总体业务功能中,无需本身显式调用(模板方法会根据运行时信息动态调用)工厂方法模式就是模板方法模式的一个应用,只不过就是抽象父类中的抽象方法特化为一个建立实例的方法,将实例的建立延迟到了子类。
例如Person
类有一个获取本身交通工具的抽象方法getVehicle
,而且可以在其余地方调用该Vehicle
暴露的属性、方法,而将Vehicle
实例的获取延迟到了子类(说是延迟,由于自己是抽象类,是没法被实例化的,所以在实例化Person
的具体子类时可以确保其getVehicle
已被重写了)。
以下是示例代码:
public class Vehicle {
private String name;
public Vehicle(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Bicycle extends Vehicle {
public Bicycle() {
super("自行车");
}
}
public class JeepCar extends Vehicle {
public JeepCar() {
super("小汽车");
}
}
public abstract class Person {
public void useVehicle() {
System.out.println("使用交通工具"+getVehicle().getName()+"来代步");
}
protected abstract Vehicle getVehicle();
}
public class Student extends Person {
@Override
protected Vehicle getVehicle() {
return new Bicycle();
}
}
public class Boss extends Person {
@Override
protected Vehicle getVehicle() {
return new JeepCar();
}
}
public class Client {
public static void main(String[] args) {
Person p = new Student();
p.useVehicle();
p = new Boss();
p.useVehicle();
}
}
复制代码
Factory Method
,抽象父类中将某类实例的获取延迟到具体子类,但本类中的其余方法能够调用该方法并认为已获取到了该类实例,而后进行属性、方法的访问Concrete Method
,真正的获取并返回所需实例的逻辑Lazy loading
懒加载模式,instance
会在getInstance
第一次被调用时被初始化
public class LazyLoadingSingleton {
private LazyLoadingSingleton() {
}
private static LazyLoadingSingleton instance;
public static LazyLoadingSingleton getInstance() {
if (instance == null) {
instance = new LazyLoadingSingleton();
}
return instance;
}
}
复制代码
Synchronized Block Singleton
上述代码在多线程并发执行时会出现instance
被屡次赋值的问题,为此可用内部锁语义Synchronized
解决
public class SynchronizerBlockSingleton {
private SynchronizerBlockSingleton() {
}
private static SynchronizerBlockSingleton instance;
public static SynchronizerBlockSingleton getInstance() {
synchronized (SynchronizerBlockSingleton.class) {
if (instance == null) {
instance = new SynchronizerBlockSingleton();
}
}
return instance;
}
}
复制代码
Double-Checked Lock
双重检查锁定,上述代码先锁定,而后检查,会致使instance
被赋值后synchronized
对后续并发调用getInstance
带来上下文切换的开销,为此能够在锁定前先检查一次
public class DoubleCheckedSingleton {
private DoubleCheckedSingleton() {
}
private static DoubleCheckedSingleton instance;
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
复制代码
DCL with volatile
因为指令重排序可能会致使new
关键字初始化对象还未完成就返回对象的内存地址,进而致使后续访问instance
属性时抛空指针异常,须要使用volatile
保证对象初始化完毕后才返回引用地址
public class VolatileSingleton {
private VolatileSingleton() {
}
private static volatile VolatileSingleton instance;
public static VolatileSingleton getInstance() {
if (instance == null) {
synchronized (VolatileSingleton.class) {
if (instance == null) {
instance = new VolatileSingleton();
}
}
}
return instance;
}
}
复制代码
Eager Mode
饿汉模式,若是实例对象的初始化开销较小(占用内存、初始化时间),那么彻底能够在类初始化时完成
public class EagerSingleton {
private EagerSingleton() {
}
private static EagerSingleton instance = new EagerSingleton();
public static EagerSingleton getInstance() {
return instance;
}
}
复制代码
instance
会在类初始化时被初始化,类只会在发生主动引用时被初始化一次,由JVM来保证
Instance Holder
若是你仍想使用懒汉模式又想优雅些,则可以使用静态内部类的方式
public class InstanceHolderSingleton {
private static class SingletonHolder {
private static InstanceHolderSingleton instance = new InstanceHolderSingleton();
}
private InstanceHolderSingleton() {
}
public static InstanceHolderSingleton getInstance() {
return SingletonHolder.instance;
}
}
复制代码
初始化InstanceHolderSingleton
时并不会初始化其静态内部类SingletonHolder
,只有在调用InstanceHolderSingleton.getInstance()
时,instance
才会随着SingletonHolder
的初始化而初始化。
如下种状况会当即致使类的初始化:
new
关键字建立该类实例java.reflect
包下的类反射访问该类,如Class.forName
枚举类的优雅
JVM也会保证枚举实例在初始化枚举时被初始化一次
public class EnumSingleton {
private EnumSingleton() {
}
private static EnumSingleton instance;
private enum InstanceEnum{
INSTANCE;
private EnumSingleton instance;
InstanceEnum() {
instance = new EnumSingleton();
}
}
public static EnumSingleton getInstance() {
return InstanceEnum.INSTANCE.instance;
}
}
复制代码
建造者模式一般应用于须要经过一系列复杂步骤才能获得最终实例的状况。就像建造房子同样,咱们须要通过打地基、搭建框架、添砖加瓦、粉饰美化等一系列步骤才能获得最终能住人的房子。而且,这些步骤能够个性化定制,例若有人喜欢欧美风格的,那么房屋框架顶部就要打形成锥形的;有人喜欢粉色,那就能够铺上粉色的墙纸……
本例中,咱们以一个邮件内容String
实例的生成来演示Builder
设计模式的应用。
public abstract class EmailBuilder {
protected String content = "";
public String getContent() {
return content;
}
public abstract void makeTitle(String title);
public abstract void makeBody(String body);
public abstract void makeGreeting(String greeting);
}
public class TextEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(title).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(body).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(greeting).append("\n");
content = stringBuilder.toString();
}
}
public class HTMLEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<h3>").append(title).append("</h3>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<p style=\"font-family: Microsoft Ya Hei; font-size: 16px\">").append(body).append("</p>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<i>").append(greeting).append("</i>").append("\n");
content = stringBuilder.toString();
}
}
public class Director {
private EmailBuilder emailBuilder;
public Director(EmailBuilder emailBuilder) {
this.emailBuilder = emailBuilder;
}
public void construct() {
emailBuilder.makeTitle("About Interview");
emailBuilder.makeBody("We are honor to tell you that you can participate our interview.");
emailBuilder.makeGreeting("Good Luck!");
}
}
复制代码
其中EmailBuilder
中声明了实例的初始状态(空串)和构建实例的一系列过程,而TextEmailBuilder
和HTMLEmailBuilder
则对这一系列过程进行了个性化实现。最终Director
是建造实例整个过程的监工,由它确保实例的成型规则地经历了哪些建造过程。
Builder
,定义了产品成型须要通过的一系列工艺(接口方法)ConcreteBuilder
,针对每道工艺进行个性化处理Director
,监工,根据产品成型流程调用接口方法打造产品Client
,模式使用者,通知Director
根据传入的ConcreteBuilder
打造特定风格的产品AbstractFactory
其实就是包含了一系列Factory Method
的类,只不过这些Factory Method
生成的实例都是相互关联的,一块儿组成某个共同体,少了谁都不行。
例如汽车Car
须要汽车外壳Facade
、轮胎Wheel
、发动机Engine
等部件,那么咱们就能够建立一个CarFactory
抽象工厂,其中声明了一系列部件的获取(抽象方法,不关心该部件是哪一个厂家生产的或是哪一个牌子的),并提供了产品的构造过程(调用这一系列抽象方法获取所需部件组装成车)
public class Engine {
String name;
public Engine(String name) {
this.name = name;
}
}
复制代码
public class Wheel {
String name;
public Wheel(String name) {
this.name = name;
}
}
复制代码
public class Facade {
String name;
public Facade(String name) {
this.name = name;
}
}
复制代码
public class Car {
Engine engine;
Wheel wheel;
Facade facade;
public Car(Engine engine, Wheel wheel, Facade facade) {
this.engine = engine;
this.wheel = wheel;
this.facade = facade;
}
}
复制代码
public abstract class CarFactory {
public Car getCar() {
return new Car(getEngine(), getWheel(), getFacade());
}
public abstract Engine getEngine();
public abstract Wheel getWheel();
public abstract Facade getFacade();
}
复制代码
public class CustomEngine extends Engine {
public CustomEngine() {
super("自定义牌发动机");
}
}
复制代码
public class CustomWheel extends Wheel{
public CustomWheel() {
super("自定义牌轮胎");
}
}
复制代码
public class CustomFacade extends Facade {
public CustomFacade() {
super("自定义牌车壳");
}
}
复制代码
public class CustomCarFactory extends CarFactory{
@Override
public Engine getEngine() {
return new CustomEngine();
}
@Override
public Wheel getWheel() {
return new CustomWheel();
}
@Override
public Facade getFacade() {
return new CustomFacade();
}
}
复制代码
public class Client {
public static void main(String[] args) {
CarFactory carFactory = new CustomCarFactory();
Car car = carFactory.getCar();
System.out.println("custom car -> " + car.engine.name + "+" + car.wheel.name + "+" + car.facade.name);
}
}
复制代码
类的功能层次结构
经过继承咱们可以继承基类已有的功能,在此之上咱们可以:重写基类已有功能(使该功能具有本类特点)、也能够新增功能,重写(这里的重写特指重写基类的已有实现)或新增功能的子类与基类构成类的功能层次结构
public class Animal {
public void eat() {
System.out.println("动物会觅食");
}
}
public class Bird extends Animal {
@Override
public void eat() {
System.out.println("鸟觅食虫子");
}
public void fly() {
System.out.println("鸟会飞");
}
}
复制代码
类的实现层次结构
经过继承,咱们可以实现基类的抽象方法,经过运用Template Method
,咱们能够将子类的逻辑注入到基类模板过程当中,这时新增子类仅为了实现基类的抽象方法,子类和基类构成类的实现层次结构
public abstract class Animal {
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
public class Snake extends Animal {
@Override
public void lockTarget() {
System.out.println("锁定猎物");
}
@Override
public void quickAttack() {
System.out.println("迅速咬住猎物喉部");
}
@Override
public void swallow() {
System.out.println("一口吞掉整个猎物");
}
}
复制代码
若是咱们将两个例子的Animal
整合在一块儿:
public abstract class Animal {
public void eat() {
System.out.println("动物会觅食");
}
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
复制代码
你会发现,Bird
没法编译,做为具体子类它必须实现抽象方法lockTarget
、quickAttack
、swallow
,可是咱们新增Bird
的初衷只是为了继承Animal
的eat
方法,并新增一个本身会fly
的功能。
这时就须要咱们将类的功能层次和实现层次分开了
public abstract class Animal {
private Hunt hunt;
public Animal(Hunt hunt) {
this.hunt = hunt;
}
public void eat() {
System.out.println("动物会觅食");
}
public void hunt() {
hunt.hunt();
}
}
public abstract class Hunt {
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
public class Bird extends Animal {
public Bird(Hunt hunt) {
super(hunt);
}
@Override
public void eat() {
System.out.println("鸟觅食虫子");
}
public void fly() {
System.out.println("鸟会飞");
}
}
public class DefaultHunt extends Hunt {
@Override
public void lockTarget() {
System.out.println("用眼睛锁定猎物");
}
@Override
public void quickAttack() {
System.out.println("快速咬死猎物");
}
@Override
public void swallow() {
System.out.println("一口一口吃掉猎物");
}
}
public class Snake extends Animal {
public Snake(Hunt hunt) {
super(hunt);
}
}
public class SnakeHunt extends Hunt {
@Override
public void lockTarget() {
System.out.println("红外线感知锁定猎物");
}
@Override
public void quickAttack() {
System.out.println("使用尖牙和毒液快速致死猎物");
}
@Override
public void swallow() {
System.out.println("一口吞掉整个猎物");
}
}
public class Client {
public static void main(String[] args) {
Hunt defaultHunt = new DefaultHunt();
Hunt snakeHunt = new SnakeHunt();
Animal snake = new Snake(snakeHunt);
System.out.println("蛇开始狩猎==========");
snake.hunt();
Animal bird = new Bird(defaultHunt);
System.out.println("鸟开始狩猎===========");
bird.hunt();
System.out.println("鸟有不一样于通常动物的功能");
((Bird) bird).fly();
}
}
复制代码
如上,Animal
、Bird
、Snake
组成功能层次结构、Hunt
、DefaultHunt
、Snake
则组成了实现层次结构。这样,之后若是咱们想扩展功能(重写或新增),那么就能够找对应功能层次结构中的类继承;若是想针对狩猎方式进行个性化实现,则继承实现层次结构中的类便可。
桥接模式避免了将实现和扩展捆绑在一块儿,减小底层类扩展基类的压力
假如你是一个农场主,须要按照顾客的需求从已采摘的苹果中挑选出符合顾客标准的苹果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Apple {
private AppleColorEnum color;
private double weight;
public enum AppleColorEnum {
RED,YELLOW,GREEN
}
}
复制代码
因而你编写了以下挑选苹果的业务处理类
public class AppleService {
List<Apple> findAppleByColor(List<Apple> apples, Apple.AppleColorEnum color) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if (Objects.equals(apple.getColor(),color)){
res.add(apple);
}
}
return res;
}
}
复制代码
可是若是你遇到了一个刁钻的顾客,他不只要求颜色为红色,并且还要求重量在500g以上呢?你能够再添加一个findRedAndWeightGreatThan500
,可是每一个顾客可能对颜色和重量的标准都是不同的,而且你没法估量有哪些顾客,对应有哪些挑选标准。
这时就须要把挑选标准(实际上就是一个算法)单独抽离出来,分析输入和输出:
Apple
,给你一个苹果boolean
,该苹果是否符合标准public interface AppleFilterStrategy {
/** * 若是该苹果符合挑选标准,那么就返回true * @param apple * @return */
boolean filterApple(Apple apple);
}
复制代码
这时,挑选苹果业务类就无需预知和预置众多挑选苹果的方法了,由于挑选策略交给了AppleFilterStrategy
public class AppleService {
List<Apple> findApple(List<Apple> apples, AppleFilterStrategy strategy) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if (strategy.filterApple(apple)) {
res.add(apple);
}
}
return res;
}
}
复制代码
客户端能够根据客户提出的挑选需求,经过匿名类的方式随意地注入挑选策略
public class Client {
public static void main(String[] args) {
// 农场主采摘的苹果
List<Apple> apples = Arrays.asList(
new Apple(Apple.AppleColorEnum.RED, 200),
new Apple(Apple.AppleColorEnum.RED, 400),
new Apple(Apple.AppleColorEnum.RED, 600),
new Apple(Apple.AppleColorEnum.YELLOW, 100),
new Apple(Apple.AppleColorEnum.YELLOW, 500),
new Apple(Apple.AppleColorEnum.YELLOW, 900),
new Apple(Apple.AppleColorEnum.GREEN, 400),
new Apple(Apple.AppleColorEnum.GREEN, 500),
new Apple(Apple.AppleColorEnum.GREEN, 600)
);
AppleService appleService = new AppleService();
// A顾客须要红色的重量大于500g的苹果
List<Apple> res1 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.RED) &&
apple.getWeight() >= 500;
}
});
System.out.println(res1);
System.out.println("======================");
// B顾客须要青色的种类小于400的
List<Apple> res2 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.GREEN) &&
apple.getWeight() <= 400;
}
});
System.out.println(res2);
}
}
复制代码
在Java8以后,像AppleFilterStrategy
这种只包含一个接口方法的接口能够被标注为@FunctionalInterface
,并使用Lambda
表达式替代冗余的匿名类
@FunctionalInterface
public interface AppleFilterStrategy {
boolean filterApple(Apple apple);
}
复制代码
List<Apple> res3 = appleService.findApple(apples, apple -> apple.getColor() != Apple.AppleColorEnum.GREEN && apple.getWeight() >= 300);
System.out.println(res3);
复制代码
策略模式的核心思想就是将复杂多变的算法逻辑抽取出来,交给客户端实现,而业务层只负责应用客户端传递的算法实现
Strategy Interface
,策略接口,定义了某个特定的算法Service
,应用策略接口的算法,面对抽象编程Client
,调用Service
时注入具体的算法实现Concrete Strategy
,具体的算法实现,一般以匿名类的形式存在,Java8以后可用Lambda
代替混合模式的典型应用就是文件系统,一个目录Directory
中能够存放若干条目Entry
,每一个条目既能够是目录又能够是文件File
。混合模式的目的就是让容器(如目录)和容器中的内容(如目录或文件)有着一致性的外观(如Entry
)
public abstract class Entry {
private String name;
private int size;
private List<Entry> items;
public Entry(String name, int size) {
this.name = name;
this.size = size;
items = new ArrayList<>();
}
public void addEntry(Entry entry) {
this.items.add(entry);
}
public abstract void print(int... layer);
}
public class Directory extends Entry {
public Directory(String name, int size) {
super(name, size);
}
@Override
public void print(int... layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName());
getItems().forEach(entry -> entry.print(n + 1));
}
}
public class File extends Entry {
public File(String name, int size) {
super(name, size);
}
@Override
public void print(int... layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName() + " size=" + getSize()+"kb");
getItems().forEach(entry -> entry.print(n + 1));
}
}
public class Client {
public static void main(String[] args) {
Entry root = new Directory("/root", 2);
Entry bin = new Directory("/bin", 0);
root.addEntry(bin);
Entry usr = new Directory("/usr", 1);
root.addEntry(usr);
Entry etc = new Directory("/etc", 0);
root.addEntry(etc);
Entry local = new Directory("/local", 3);
usr.addEntry(local);
Entry java = new File("java.sh", 128);
local.addEntry(java);
Entry mysql = new File("mysql.sh", 64);
local.addEntry(mysql);
Entry hadoop = new File("hadoop.sh", 1024);
local.addEntry(hadoop);
root.print();
}
}
/root
/bin
/usr
/local
java.sh size=128kb
mysql.sh size=64kb
hadoop.sh size=1024kb
/etc
复制代码
组件
如本例的Entry
,是容器和容器内容的同一外观。系统不直接操做容器和容器中的内容,而是操做组件
容器
其中可包含若干容器和条目
条目
系统中的基本单元
装饰者模式是一种结合继承和组合(委托)设计模式,经过继承可以实现统一外观(装饰类和目标类有共同的父类),经过委托可以在目标功能的基础之上进行加强。而且装饰类无论目标类是源目标类仍是被装饰过的目标类,它对目标类是否已被保存过和被哪一种包装器(装饰类)包装过以及被包装了几层都不关心,它只负责当前的装饰逻辑。
本例中,有一个糕点师Baker
,他有一个bake
烘焙面包的抽象方法,BreadBaker
则是他的一个实现。如今咱们须要根据顾客的不一样口味对原味的面包进行包装,例如应该加哪些佐料Ingredient
,如下是示例代码
public abstract class Baker {
public abstract void bake();
}
public class BreadBaker extends Baker {
@Override
public void bake() {
System.out.println("面包被烘焙");
}
}
复制代码
public class IngredientDecorator extends Baker {
private Baker baker;
private String ingredient;
public IngredientDecorator(Baker baker,String ingredient) {
this.baker = baker;
this.ingredient = ingredient;
}
@Override
public void bake() {
System.out.print("添加了" + ingredient + "的");
baker.bake();
}
}
复制代码
public class Client {
public static void main(String[] args) {
Baker baker = new BreadBaker();
baker.bake();
Baker pepper = new IngredientDecorator(baker, "胡椒粉");
pepper.bake();
Baker mustard = new IngredientDecorator(baker, "芥末");
mustard.bake();
Baker oliveOil = new IngredientDecorator(baker, "橄榄油");
oliveOil.bake();
Baker pepperAndOlive = new IngredientDecorator(new IngredientDecorator(baker, "橄榄油"), "胡椒粉");
pepperAndOlive.bake();
Baker mustardAndOliveAndPepper =
new IngredientDecorator(
new IngredientDecorator(
new IngredientDecorator(baker, "胡椒粉"),
"橄榄油"),
"芥末");
mustardAndOliveAndPepper.bake();
}
}
面包被烘焙
添加了胡椒粉的面包被烘焙
添加了芥末的面包被烘焙
添加了橄榄油的面包被烘焙
添加了胡椒粉的添加了橄榄油的面包被烘焙
添加了芥末的添加了橄榄油的添加了胡椒粉的面包被烘焙
复制代码
固然本例只是单纯演示装饰者模式的思想,你彻底能够将Ingredient
具体化为PepperIngredient
、MustardIngredident
等代替字符串魔法值
应用装饰者模式有一个口诀:是你(IS-A)还有你(HAS-A),一切拜托你(全部的重写方法委托给目标类对象,在此之上本身能够添加当前这一层包装的逻辑)。
Parent
,装饰类和目标类有一个共同的父类,经过在父类声明抽象方法使得二者有共同的外观Target
,目标类,须要被包装的类Decorator
,装饰类,既能够直接装饰目标对象,又能够装饰装饰过目标对象的装饰对象,由于二者有共同的外观visitor
模式目的是将数据结构的管理(对元素的增删改查)和对数据结构的访问处理逻辑分离开,一般和迭代器模式结合使用。咱们将对数据访问处理的逻辑单独定义一个Visitor
接口以及声明相应的visit(E element)
方法,而数据接口则对应提供一个受理Visitor
的accept(Visitor visitor)
方法(其实就是简单的调用visitor.visit()
。visit(E element)
至关于对访问到的元素进行消费。
如下是数据结构为多叉树时的示例代码(其中迭代器用的是前文实现的而非JDK自带的)
@Data
public abstract class Node<E> implements Iterable<Node<E>> {
private E element;
private List<Node<E>> children;
public Node(E element) {
this.element = element;
}
public abstract void accept(NodeVisitor<E> visitor);
@Override
public Iterator<Node<E>> iterator() {
return new NodeIterator(this);
}
}
public class Leaf<E> extends Node<E> {
public Leaf(E element) {
super(element);
this.setChildren(Collections.emptyList());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitLeaf(this);
}
}
public class Branch<E> extends Node<E> {
public Branch(E elemnt) {
super(elemnt);
this.setChildren(new ArrayList<>());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitBranch(this);
}
}
复制代码
public class NodeIterator<E> implements Iterator<Node<E>> {
private Node<E> root;
private Stack<Node<E>> stack;
public NodeIterator(Node<E> root) {
this.root = root;
this.stack = new Stack<>();
stack.push(root);
}
@Override
public boolean hasNext() {
return stack.size() > 0;
}
@Override
public Node<E> next() {
if (!hasNext()) {
throw new RuntimeException("no more elements");
}
Node<E> node = stack.pop();
List<Node<E>> children = node.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
return node;
}
}
复制代码
public interface NodeVisitor<E> {
void visitLeaf(Leaf<E> leaf);
void visitBranch(Branch<E> branch);
}
public class PrintNodeVisitor<E> implements NodeVisitor<E> {
@Override
public void visitLeaf(Leaf<E> leaf) {
System.out.print(leaf.getElement()+" ");
}
@Override
public void visitBranch(Branch<E> branch) {
System.out.print(branch.getElement()+" ");
}
}
public class PlusOneNodeVisitor implements NodeVisitor<Integer> {
/** * 访问到叶子节点则将节点值+1 * @param leaf */
@Override
public void visitLeaf(Leaf<Integer> leaf) {
leaf.setElement(leaf.getElement() + 1);
}
/** * 访问到分叉节点,则将节点值+其孩子节点数 * @param branch */
@Override
public void visitBranch(Branch<Integer> branch) {
branch.setElement(branch.getElement() + branch.getChildren().size());
}
}
复制代码
visitor
模式将对数据结构的访问逻辑经过accept
委托给Visitor
接口和迭代器模式将遍历访问逻辑经过createIterator
委托给Iterator
接口有殊途同归之妙。将专业的事交给专业的人作,知足Single Responsibility
和Interface Segregation Principle
;须要修改访问处理逻辑咱们只须要新增一个NodeVisitor
的实现,知足Open/Closed Principle
;逻辑实现和客户端代码都面向接口NodeVisitor
编程,知足Dependency Inversion
。
Visitor
,拜访者,声明相关的visit
方法,用于对数据结构进行访问处理DataStructure
,数据结构,提供元素的增删改查接口,经过accept(visitor)
将访问处理逻辑交给Visitor
ConcreteVisitor
,根据业务所需,实现具体的访问处理逻辑责任链模式经过维护若干请求受理者造成一条链来处理客户端发起的请求,该模式最大的优势在于它弱化了客户端和请求受理者之间的关系,客户端只须要将请求发送到责任链,在责任链中经过连环委托的机制就可以作到没法受理请求的人直接忽略请求,而可以受理请求的人截断请求并受理。
客户端不用关心请求具体会被谁受理,这样就提升了客户端的独立性。
本例中,存在一条公司组织架构责任链(Employee->Leader->Manager->Boss
),他们都可以受理报销费用的请求handle(int amount)
,Client
无需关心多少面值的报销金额应该由谁来受理
public abstract class Handler {
private Handler handler = null;
public abstract void handle(int amount);
public Handler setNext(Handler handler) {
this.handler = handler;
return handler;
}
public Handler getNext() {
return handler;
}
}
public class Employee extends Handler {
@Override
public void handle(int amount) {
if (amount <= 100) {
System.out.println("$" + amount + " is handled by employee");
return;
}
this.getNext().handle(amount);
}
}
public class Leader extends Handler{
@Override
public void handle(int amount) {
if (amount <= 1000) {
System.out.println("$" + amount + " is handled by leader");
return;
}
this.getNext().handle(amount);
}
}
public class Manager extends Handler{
@Override
public void handle(int amount) {
if (amount <= 5000) {
System.out.println("$" + amount + " is handled by manager");
return;
}
this.getNext().handle(amount);
}
}
public class Boss extends Handler {
@Override
public void handle(int amount) {
System.out.println("$" + amount + " is handled by boss");
}
}
复制代码
public class Client {
public static void main(String[] args) {
Handler employee = new Employee();
Handler leader = new Leader();
Handler manager = new Manager();
Handler boss = new Boss();
employee.setNext(leader).setNext(manager).setNext(boss);
for (int i = 0; i < 6; i++) {
int amount = (int) (Math.random() * 10000);
employee.handle(amount);
}
}
}
$6643 is handled by boss
$4964 is handled by manager
$684 is handled by leader
$9176 is handled by boss
$8054 is handled by boss
$909 is handled by leader
复制代码
《图解设计模式》