设计模式的六大原则和23种设计模式
1、前言
以前看过不少设计模式的书籍:《设计模式之禅》、《大话设计模式》等,内容到不难,但是由于缺乏实践,很容易就忘了。工作后,发现很多时候可以通过巧妙的设计开解决一些编码难题。优秀的编码设计可以省下不少功夫,工预善其事,必先利其器。为此重新拾起设计模式,为了提高编程水平。
- 提高思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
“设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。”
2、设计模式的六大原则
设计模式的目的是为了更好的代码重用性,可读性,可靠性和可维护性。常用的六大设计模式有:单一职责原则(SRP),里氏替换原则(LSP),依赖倒转原则(DIP),接口隔离原则(ISP),迪米特法则(LOD),开闭原则(OCP)。
2.1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2.2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
2.3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
2.4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
2.5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
2.6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
3、设计模式
3.1、单例模式(Singleton Pattern)
单例模式(Singleton Pattern),就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
//饿汉模式 public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton() { //私有构造函数 } public static Singleton getSingleton() { return singleton; } public static void doSomething() { //尽量为static方法 } }
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
3.2、工厂方法模式(Factory Pattern)
工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。
//对象接口 public interface Product { } //抽象工厂 public abstract class AbstractFactory { public abstract Product createProduct(); }
不同的产品类实现相同的接口:
public class ProductOne implements Product { } public class ProductTwo implements Product { }
构建工厂方法,集成自抽象工厂类,可以创建不同的产品:
public class FactoryOne extends AbstractFactory { @Override public Product createProduct() { return new ProductOne(); } } public class FactoryTwo extends AbstractFactory { @Override public Product createProduct() { return new ProductTwo(); } }
使用工厂模式创建不同类型的对象:
AbstractFactory FactoryOne = new FactoryOne(); Product product1 = FactoryOne.createProduct(); AbstractFactory FactoryTwo = new FactoryOne(); Product product2 = FactoryTwo.createProduct();
3.3、抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
看如下的代码示例:
//抽象产品类 public abstract class AbstractProductA { public abstract void doSomething(); }
两个具体的产品:
public class ProductA1 extends AbstractProductA { @Override public void doSomething() { } } public class ProductA2 extends AbstractProductA { @Override public void doSomething() { } }
抽象工厂类:
public abstract class AbstractCreator{ public abstract AbstractProductA createAbstractProductA(); }
创建具体的产品由具体的实现类来完成:
public class CreateA1 extends AbstractCreator{ @Override public AbstractProductA createAbstractProductA() { return new ProductA1(); } } public class CreateA2 extends AbstractCreator{ @Override public AbstractProductA createAbstractProductA() { return new ProductA2(); } }
3.4、模板方法模式(Template Method Pattern)
模板方法模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。父类定义好了部分方法的实现,通过调用已有的但未实现的抽象方法。子类通过实现这些具体的抽象方法,再调用父类已实现好的通用方法templateMethod(),而且通常用final关键字修饰。
public abstract class AbstractClass{ protected abstract void doSomething(); protected abstract void doAnything(); //父类定义好的模板方法,一般用final修饰,子类不再修改。 public final void templateMethod() { this.doSomething(); this.doAnything(); } }
继承父类,写两个不同的子类:
public class ConcreteOne extends AbstractClass{ @Override protected void doSomething() { } @Override protected void doAnything() { } } public class ConcreteTwo extends AbstractClass{ @Override protected void doSomething() { } @Override protected void doAnything() { } }
看看具体如何使用,两个不同的子类都实现了各自的方法,再调用通用的父类方法。可以避免在两个子类中再去编写重复的代码:
public void test() { AbstractClass abstractClass = new ConcreteOne(); abstractClass.templateMethod(); AbstractClass abstractClass2 = new ConcreteTwo(); abstractClass2.templateMethod(); }
3.5、建造者模式(Builder Pattern)
建造者模式也叫生成器模式:将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。
产品类:
public class Product{ public void doSomething() { } }
抽象的建造者:
public abstract class Builder{ public abstract void setPart(); public abstract Product builderProduct(); }
不同类型Product对象的建造者类,继承自Builder抽象类,负责去实现不同类型Product对象构造的具体细节:
public class ConcreteProductABuilder extends Builder{ private Product product = new Product(); @Override public void setPart() { //不同的productA设置 } @Override public Product builderProduct() { return product; } } public class ConcreteProductBBuilder extends Builder{ private Product product = new Product(); @Override public void setPart() { //不同的productB设置 } @Override public Product builderProduct() { return product; } }
使用建造者模式,去创建不同类型的对象:
private Builder builderA = new ConcreteProductABuilder(); Product productA = builderA.builderProduct(); private Builder builderB = new ConcreteProductBBuilder(); Product productB = builderB.builderProduct();
小结:还是有点麻烦的,不同类型的Product对象创建都需要一种对于的Builder对象,而且可以发现这个例子,同一个Builder示例,创建一个类型的Product所返回的是同一个对象实例(这个例子还可以改进),这里可能是每创建一个Product对象就需要一个Builder对象实例一一对应(如果需要创建真正不同的Product对象)。不过对于大量创建确实省事,确实抽象+分离了不同Product构造的重复过程,在不同的构造Builder再去实现具体不同的设置。
3.6、代理模式(Proxy Pattern)
代理模式是一种效率非常高的模式。为其它对象提供一种代理以控制这个对象的访问。
抽象接口类,定义同一的方法,实体类和代理类都实现这个接口,这样就有一一对应的方法。
public interface Subject{ public void request(); }
实体类:
public class RealSubject implements Subject{ public void request(){ } }
代理类:
//代理 public class Proxy implements Subject{ private Subject subject = null; public Proxy(){ this.subject = new Proxy(); } public Proxy(Subject subject){ this.subject = subject; } public void request(){ this.before(); this.subject.request(); this.after(); } public void before(){ } public void after(){ } }
使用代理,就和直接使用对象一样,有相同的方法可以调用(它们一一对应):
//Proxy和Subject实现相同的接口,保证有相同的方法,调用proxy代理的方法,来间接调用Subject对象的方法,这就是代理。 Proxy proxy = new Proxy(new Subject());
3.6.1、动态代理
动态代理首要条件,被代理类必须实现一个接口。在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。
抽象接口:
public interface Subject{ public void doSomething(); }
实现接口的类:
public class RealSubject implements Subject{ @Override public void doSomething() { } }
动态代理用到的InvocationHandler实体类,实现InvocationHandler接口:其中关键的是要重写invoke()方法
public static class MyInvocationHandler implements InvocationHandler{ private Object targetObject = null; public MyInvocationHandler(Object object) { this.targetObject = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception{ return method.invoke(this.targetObject, args); } }
动态代理:
public static class DynamicProxy<T> { public static <T> T newProxyInstance(Subject subject) { ClassLoader classLoader = subject.getClass().getClassLoader(); Class<?>[] clases = subject.getClass().getInterfaces(); InvocationHandler h = new MyInvocationHandler(subject); return (T)Proxy.newProxyInstance(classLoader, clases, h); } }
使用动态代理:
Subject subject = new RealSubject(); Subject proxy = DynamicProxy.newProxyInstance(subject); //通过代理就可以调用被代理对象的全部接口中的方法 proxy.doSomething();
3.7、原型模式(ProtoType Pattern)
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
必须实现Cloneable接口,这个接口中一个方法都没有,只是作为一个标记,告诉JVM这个标记的对象可以拷贝。
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); }
可以看到是重写了clone()方法,但是Cloneable接口中没有方法,这个是从哪里来的?是从Object类中的clone()方法,每个类都默认继承Object,覆写Object中的clone()方法。
原型模式通用代码:
// 原型模式通用代码 public static class ProtoTypeClass implements Cloneable @Override protected ProtoTypeClass clone() { ProtoTypeClass protoTypeClass = null; try { protoTypeClass = (ProtoTypeClass) super.clone(); } catch (CloneNotSupportedException e) { // clone失败异常处理 } return protoTypeClass; } }
构造函数在clone的过程中是不会被执行的:clone的原理是直接从内存堆栈中以二进制流的方式进行拷贝,重新分配一块内存存储拷贝的数据。
// 原型模式通用代码 public static class ProtoTypeClass implements Cloneable public ProtoTypeClass() { System.out.println("ProtoTypeClass Construct"); ... ... } public static void main(String[] args) { ProtoTypeClass protoTypeClass = new ProtoTypeClass() ProtoTypeClass cloneProtoTypeClass = protoTypeClass.clone() }
从执行结果可以看到只执行一次构造函数:
ProtoTypeClass Construct
3.7.1、深拷贝与浅拷贝
Java的clone拷贝是一个偷懒的拷贝:只会拷贝原始类型:int、long、char等,包括String类型。
public static class ProtoTypeClass implements Cloneable { public ArrayList<String> arrayList = new ArrayList<String>(); public void setValue(String value) { this.arrayList.add(value); } public ArrayList<String> getValue(){ return this.arrayList; } } public static void main(String[] args) { ProtoTypeClass protoTypeClass = new ProtoTypeClass(); protoTypeClass.setValue("protoTypeClass"); System.out.println(protoTypeClass.getValue()); ProtoTypeClass cloneProtoTypeClass = protoTypeClass.clone(); cloneProtoTypeClass.setValue("cloneProtoTypeClass"); System.out.println(protoTypeClass.getValue()); }
执行结果看到,对clone后的对象进行操作,再看看原始对象的数据发生了变化:
[protoTypeClass] [protoTypeClass, cloneProtoTypeClass]
如何进行深拷贝:对所有的私有对象进行一次lone在复制给新clone的对象。
@Override protected ProtoTypeClass clone() { ProtoTypeClass protoTypeClass = null; try { protoTypeClass = (ProtoTypeClass) super.clone(); protoTypeClass.arrayList = (ArrayList<String>) this.arrayList.clone(); } catch (CloneNotSupportedException e) { // clone失败异常处理 } return protoTypeClass; }
还有更高级的操作,重写二进制流来操作对象的底层复制,实现深拷贝。深拷贝与 浅拷贝分开实现,不要混用。
3.7.2、clone与final
要实现clone方法,类成员变量不要加final修饰。否则无法实现深拷贝:
3.8、中介者模式(Mediator Pattern)
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
抽象中介者:
public abstract class Mediator{ protected ConcreteColleague1 concreteColleague1; protected ConcreteColleague2 concreteColleague2; public ConcreteColleague1 getConcreteColleague1() { return concreteColleague1; } public void setConcreteColleague1(ConcreteColleague1 concreteColleague1) { this.concreteColleague1 = concreteColleague1; } public ConcreteColleague2 getConcreteColleague2() { return concreteColleague2; } public void setConcreteColleague2(ConcreteColleague2 concreteColleague2) { this.concreteColleague2 = concreteColleague2; } public abstract void doSomething1(); public abstract void doSomething2(); }
中介者实体类:
public class ConcreteMediator extends Mediator{ @Override public void doSomething1() { super.concreteColleague1.selfMethod1(); super.concreteColleague2.selfMethod2(); } @Override public void doSomething2() { super.concreteColleague1.selfMethod1(); super.concreteColleague2.selfMethod2(); //需要注意避免死循环 //super.concreteColleague2.depMethod2(); } }
抽象同事类:
public abstract class Colleague{ protected Mediator mediator; public Colleague(Mediator mediator) { this.mediator = mediator; } }
具体同事类:
public class ConcreteColleague1 extends Colleague{ public ConcreteColleague1(Mediator mediator) { super(mediator); } public void selfMethod1() { } public void depMethod1() { //委托给中介处理 super.mediator.doSomething1(); } } public class ConcreteColleague2 extends Colleague{ public ConcreteColleague2(Mediator mediator) { super(mediator); } public void selfMethod2() { } public void depMethod2() { //委托给中介处理 super.mediator.doSomething2(); } }
使用:
public void main() { Mediator mediator = new ConcreteMediator(); ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator); ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator); mediator.setConcreteColleague1(colleague1); mediator.setConcreteColleague2(colleague2); mediator.doSomething1(); mediator.doSomething2(); }
将中介构造出来,作为构造参数,再构造被中介的类。再将需要中介代理的类,设置给中介,通过中介去操作。同事类必须通过构造函数注入中介者对象,因为同事类必须要有中介者。而中介者中代理的同事类则通过setter/getter的方式进行设置,因为中介者可以没有或者代理任意数量的同事类。
优点:减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事只依赖于中介者,减少依赖,降低耦合。
缺点:引入中介者去执行所有的操作,会使得中介者变得异常庞大逻辑会越来越复杂,同事之间的关系越复杂,中介者中的逻辑也就越复杂,需要处理更多的业务。
3.9、命令模式(Command Pattern)
命令模式是高内聚的模式,其定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
通用Receiver类:
//通用Receiver类 public abstract class Receiver{ public abstract void doSomething(); }
具体的Receiver类:
//具体的Receiver类 public class ConcreteReceiver1 extends Receiver{ @Override public void doSomething() { } } public class ConcreteReceiver2 extends Receiver{ @Override public void doSomething() { } }
抽象的Command类:
//抽象的Command类 public abstract class Command{ public abstract void execute(); }
具体的Command类:
//具体的Command类 public class ConcreteCommand1 extends Command{ private Receiver receiver; public ConcreteCommand1(Receiver receiver) { this.receiver = receiver; } @Override public void execute() { this.receiver.doSomething(); } }
调用者Invoke类:
public class Invoker{ private Command command; public Invoker() { } public void setCommand(Command command) { this.command = command; } public void action() { this.command.execute(); } }
使用场景:
Invoker invoker = new Invoker(); invoker.setCommand(new ConcreteCommand1(new ConcreteReceiver1())); invoker.action();
优点:扩展性强,Command命令可以方便的增加和扩展,调用者Invoke和高层次的模块没有过度的代码耦合。
缺点:随着Command命令的增多,很明显,类就变多,变得膨胀,难以管理。结合模板模式可以减少Command子类膨胀的问题。
3.10、责任链模式(Chain of Responsibility Pattern)
定义:多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象形成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
定义请求等级和相应:
public class Level{ } public class Request{ public Level getRequestLevel() { return null; } } public class Response{ }
定义抽象的处理类:提供统一final类型的方法,以及设置下一个处理对象的方法。
public abstract class Handler{ private Handler nextHandler; public final Response handlerMessage(Request request) { Response response = null; if(this.getHandlerLevel().equals(request.getRequestLevel())) { response = this.echo(request); }else { if (this.nextHandler!=null) { response = this.nextHandler.handlerMessage(request); }else { // } } return response; } public void setNext(Handler handler) { this.nextHandler = handler; } protected abstract Level getHandlerLevel(); protected abstract Response echo(Request request); }
Handler实体类对象,只需要实现两个方法,具体处理和获得当前处理等级:
public class ConcreteHandler1 extends Handler{ @Override protected Level getHandlerLevel() { return null; } @Override protected Response echo(Request request) { return null; } } public class ConcreteHandler2 extends Handler{ @Override protected Level getHandlerLevel() { return null; } @Override protected Response echo(Request request) { return null; } } public class ConcreteHandler3 extends Handler{ @Override protected Level getHandlerLevel() { return null; } @Override protected Response echo(Request request) { return null; } }
来看看,具体使用,一系列处理Handler,设置依次处理的顺序即可:
Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); Handler handler3 = new ConcreteHandler3(); handler1.setNext(handler2); handler2.setNext(handler3);
3.11、装饰模式(Decorator Pattern)
装饰模式:动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
抽象出来的统一接口,要装饰的对象实现这个接口或继承这个抽象父类:
public abstract class Component{ public abstract void operator(); }
需要被修饰的对象
public class ConcreteComponent extends Component{ @Override public void operator() { // } }
抽象的装饰器类,同样的实现这个统一的接口:
public abstract class Decorate extends Component{ private Component component = null; public Decorate(Component component) { this.component = component; } public void operator() { this.component.operator(); } }
装饰器实体类:
public class ConcreteDecorate extends Decorate{ public ConcreteDecorate(Component component) { super(component); private void method() { //修饰 } @Override public void operator() { this.method(); super.operator(); } public class SecondConcreteDecorate extends Decorate{ public SecondConcreteDecorate(Component component) { super(component); private void method() { //修饰 } @Override public void operator() { this.method(); super.operator(); } }
使用装饰模式,将对象交给装饰器,还可以多次装饰。其实有点像代理模式:
//被修饰的对象 Component component = new ConcreteComponent(); //修饰 component = new ConcreteDecorate(component); //在修饰 component = new SecondConcreteDecorate(component); component.operator();
优点:继承静态的给类增加功能,而装饰模式动态的给类增加功能。独立发展不会耦合,动态的扩展一个类的实现功能。
缺点:一层层装饰器包裹下的对象会变得复杂,工作量剧增。适当的减少装饰器的数量,降低系统的复杂度。
3.12、策略模式(Strategy Pattern)
策略模式,是一种比较简单的模式。定义一组算法,将每个算法封装起来,并且使它们之间可以相互转换。用到的就是面向对象的继承和多态机制。
抽象的策略角色,作为顶层接口:
public interface Strategy{ public void doSomething(); }
具体的策略需要实现公用接口,实现内部具体的策略:
public class ConcreteStrategy implements Strategy{ public void doSomething() { } }
封装策略:
public class Context{ private Strategy strategy = null; public Context(Strategy strategy) { this.strategy = strategy; } public void doAnything() { this.strategy.doSomething(); } }
使用策略模式:
Strategy strategy = new ConcreteStrategy(); Context context = new Context(strategy); context.doAnything();
优点:避免使用多重条件去判断使用哪种策略,这得益于多态。其次,扩展性良好,添加一个新的策略只需要实现接口就行了。
缺点:每个策略都需要单独的一个类,复用性很低,会导致类的数量增多。上层模块必须知道所有的策略,从中选择合适的策略,与迪米特法则违背(想使用一个策略,为什么需要了解它的实现呢?)。
3.12.1、策略枚举
这是一个枚举。受枚举类型的限制,每个枚举项都是public、final、static,扩展性受到一定的约束,因此在系统开发中,策略枚举一般担当不了经常发生变化大额角色。
public enum Calculator{ ADD("+"){ public int exec(int a,int b) { return a+b; } }, SUB("-"){ public int exec(int a,int b) { return a-b; } }; private String value = ""; private Calculator(String _value) { value = _value; } public abstract int exec(int a,int b); }
3.13、适配器模式(Adapter Pattern)
适配器模式:将一个类的接口变换成客户端所期望的另一种接口,从而使得原本因接口不匹配而无法在一起工作的两个类能够在一起工作。又叫做变压器模式,也叫做包装模式(Wrapper)。适配器分为:类适配器(通过继承关系)和对象适配器(通过组合对象)。
一共涉及到三个角色:
Target目标:定义把其它接口最终转换成何种接口。
Adaptee源角色:需要转换的接口,需要经过适配器的包装,成为合适的角色。
Adapter适配器角色:核心中间角色,职责就是把Adaptee角色转换成为Target目标角色。
具体例子代码如下,目标角色:
public interface Target{ public void request(); }
目标角色实体类:
public class ConcreteTarget implements Target{ public void request() { // } }
源角色,需要被转换的数据源:
public class Adaptee{ public void doSomething() { // } }
适配器角色,这种通过继承关系的适配器成为类适配器:
public class Adapter extends Adaptee implements Target{ public void request() { super.doSomething(); } }
场景,将源角色通过适配器转换成目标角色:
Target target = new Adapter(); target.request();
此外,一般用到的适配器通常为对象适配器,将源角色作为构造参数传入适配器,通过组合多种源角色,利用适配器间接使用:
public class WrapperAdapter implements Target{ private Adaptee adaptee; public WrapperAdapter(Adaptee target) { this.adaptee = adaptee; } public void request() { adaptee.doSomething(); } }
使用场景:
Target target2 = new WrapperAdapter(new Adaptee()); target2.request();
3.14、迭代器模式(Iterator Pattern)
迭代器模式定义:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。迭代器模式是一个没落的模式,基本上没人会单独写一个迭代器模式,除非是产品性质的开发。
抽象迭代器接口:
public interface Iterator{ public Object next(); public boolean hasNext(); public boolean remove(); }
迭代器接口实体类,构造函数传入容器对象:
public class ConcreteIterator implements Iterator{ private Vector vector = new Vector<>(); public int cursor = 1; public ConcreteIterator(Vector vector) { this.vector = vector; } @Override public Object next() { Object result = null; if (this.hasNext()) { result = this.vector.get(this.cursor++); } return result; } @Override public boolean hasNext() { if (this.cursor==this.vector.size()) { return false; } return true; } @Override public boolean remove() { this.vector.remove(this.cursor); return true; } }
容器接口:
public interface Aggregate{ public void add(Object object); public void remove(Object object); public Iterator iterator(); }
定义容器实体类:
public class ConcreteAggregate implements Aggregate{ private Vector vector = new Vector(); @Override public void add(Object object) { this.vector.add(object); } @Override public void remove(Object object) { this.vector.remove(object); } @Override public Iterator iterator() { return new ConcreteIterator(this.vector); } }
使用迭代器:
Aggregate aggregate = new ConcreteAggregate(); aggregate.add("a"); aggregate.add("c"); aggregate.add("d"); Iterator iterator = aggregate.iterator(); while (iterator.hasNext()) { Object type = (Object) iterator.next(); }
3.15、组合模式(Composite Pattern)
组合模式也叫合成模式,有时又叫做整体模式。主要用来描述部分与整体的关系,定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的 使用具有一致性。
抽象构建:
public abstract class Component{ public void doSomething() { } }
树枝中间件:
public class Composite extends Component{ private ArrayList<Component> components = new ArrayList<Composition.Component>(); public void add(Component component) { this.components.add(component); } public void remove(Component component) { this.components.remove(component); } public ArrayList<Component> getChildren() { return this.components; } }
叶子节点:
public class Leaf extends Component{ @Override public void doSomething() { } }
使用组合模式,场景类:
Composite root = new Composite(); Leaf leaf = new Leaf(); root.add(leaf public void disPlay(Composite root) { //遍历 for (Component component:root.getChildren()) { if (component instanceof Leaf) { component.doSomething(); }else { disPlay((Composite)component); } } }
组合模式的优点:所有节点都是Component类型,使用者无需考虑使用的是单个对象还是整体组合的结构。节点可以自由的增加,符合开闭原则,扩展性强。
组合模式的缺点:叶子节点和树枝直接使用了各自的实现类,与依赖倒置原则相悖。
体现整体和局部关系的时候,而且关系可能比较深,优先考虑组合模式。
组合模式分为两种,一种是安全模式,一种是透明模式。上面的是安全模式,那么透明模式是什么呢?可以看下透明模式的类图。下图左边是透明模式,右边是安全模式。透明模式,用户看不见使用的实际是什么类型,是组合还是单个。而安全模式,保证了方法调用的有效性,不会出错。
3.16、观察者模式(Observer Pattern)
观察者模式又叫发布订阅模式,在项目中经常使用的模式。定义:定义对象间的一种一对多的依赖关系,使得每当已给对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
被观察者抽象类:
public abstract class Subject{ private Vector<Observer> observers = new Vector<Observer>(); public void addObserver(Observer o) { this.observers.add(o); } public void delObserver(Observer o) { this.observers.remove(0); } public void notifyObserver() { for (Observer observer :observers) { observer.update(); } } }
被观察者:
public class ConcreteSubject extends Subject{ public void doSomething() { //业务逻辑 super.notifyObserver(); } }
抽象观察者接口:
public interface Observer{ public void update(); }
实现观察者接口的观察者类:
public class ConcreteObserver implements Observer{ @Override public void update() { //观察者收到消息 } }
观察者模式使用起来也很方便:
Observer observer = new ConcreteObserver(); ConcreteSubject subject = new ConcreteSubject(); subject.addObserver(observer); subject.doSomething();
优点:观察者与被观察者之间抽象耦合,抽象层级使得扩展性非常强。
缺点:运行效率受到不同观察者的影响,由于是链式的逐个通知观察者,可能导致阻塞。一般考虑异步的方式。
Java其实已经提供好了完好的观察者框架:Observer和Observable,分别实现接口Observer和继承抽象类Observable即可。
3.17、门面模式(Facade Pattern)
门面模式也叫外观模式,是一种封装模式,定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得系统易于使用。
三次子系统:
public class ClassA{ public void doSomethingA() { } } public class ClassB{ public void doSomethingB() { } } public class ClassC{ public void doSomethingC() { } }
门面封装类:
public class Facade{ private ClassA a = new ClassA(); private ClassB b = new ClassB(); private ClassC c = new ClassC(); public void methodA() { a.doSomethingA(); } public void methodB() { b.doSomethingB(); } public void methodC() { c.doSomethingC(); } }
有点像是代理模式的,通过Facade门面对象间接的去访问内部对象。
优点:减少系统间的依赖;提高灵活性,不管系统内部如何变化,不会影响到外部的调用;提高安全性,避免直接操作内部对象,只能通过门面去访问。
缺点:不符合开闭原则:对修改关闭,对扩展开放。
注意:1、一个子系统可以有多个门面,不要企图用一个门面来接管所有的访问,适当的分散各个门面的职责。
2、门面不参与各个子系统的业务逻辑,否则的话就会产生依赖倒置,子系统依赖门面。是严重的设计错误,违反单一职责原则,还破坏了系统的封装性。
3.18、备忘录模式(Memento Pattern)
备忘录模式是一种提供了弥补真实世界缺陷的方法。定义:在不破坏封装性的情况下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
被备忘的类:
public class Originator { private String state = ""; public String getState() { return state; } public void setState(String state) { this.state = state; } public Memento createMemento() { return new Memento(this.state); } public void restoreMemento(Memento memento) { this.setState(memento.getState()); } }
备忘录角色:
public class Memento { private String state = ""; public Memento(String state) { this.state = state; } public String getState() { return state; } }
备忘录管理员角色:
public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
使用备忘录模式:
Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); caretaker.setMemento(originator.createMemento()); originator.restoreMemento(caretaker.getMemento());
注意:备忘录创建对象就是在“最近”的代码中使用该备份,需要管理备忘的生命周期;不要频繁的创建备忘对象,比如for循环中,控制备忘的数量,大量对象的创建消耗系统资源,系统的性能需要考虑。
可以结合克隆模式,只适用于比较简单的场景,尽量不要与其它的对象产生严重的耦合。
public static class Originator implements Cloneable { private String state = ""; private Originator backup; public String getState() { return state; } public void setState(String state) { this.state = state; } public void createMemento() { this.backup = this.clone(); } public void restoreMemento() { this.setState(this.backup.getState()); } @NonNull @Override protected Originator clone() { try { return (Originator) super.clone(); } catch (Exception e) { e.printStackTrace(); } return null; } }
还有很多需要学习的地方,以后实践检验。
3.19、访问者模式(Visitor Pattern)
访问者模式是一种相对简单的模式,定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
抽象元素:
public static abstract class Element { public abstract void doSomething(); public abstract void accept(IVisitor visitor); }
具体的不同元素:
public static class ConcreteElement1 extends Element { @Override public void doSomething() { } @Override public void accept(IVisitor visitor) { } } public static class ConcreteElement2 extends Element { @Override public void doSomething() { } @Override public void accept(IVisitor visitor) { } }
访问接口:
public static interface IVisitor { public void visit(ConcreteElement1 element1); public void visit(ConcreteElement2 element2); }
访问类:
public static class Visitor implements IVisitor { @Override public void visit(ConcreteElement1 element1) { element1.doSomething(); } @Override public void visit(ConcreteElement2 element2) { element2.doSomething(); } }
场景类:
Element element = new ConcreteElement2(); element.accept(new Visitor()); Element element1 = new ConcreteElement1(); element1.accept(new Visitor())
优点:符合单一职责原则,Visitor负责访问,具体的Element类负责数据的处理。扩展性强,灵活性高。
缺点:访问者需要关注具体的细节实现如何访问,违反迪米特法则。访问者依赖具体的对象,而不是抽象的接口,破坏依赖倒置原则。
3.20、状态模式(State Pattern)*
当一个对象在状态改变时运行其改变行为,这个对象看起来像改变了其类。状态模式的核心是类,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
抽象的环境角色
public static abstract class State { protected Context context; public void setContext(Context context) { this.context = context; } public abstract void handle1(); public abstract void handle2(); }
不同的环境状态:
public static class ConcreteState1 extends State { @Override public void handle1() { // } @Override public void handle2() { super.context.setCurrentState(Context.STATE1); super.context.handle2(); } } public static class ConcreteState2 extends State { @Override public void handle1() { super.context.setCurrentState(Context.STATE2); super.context.handle1(); } @Override public void handle2() { // } }
Context类,包含不同的环境状态:
public static class Context { public final static State STATE1 = new ConcreteState1(); public final static State STATE2 = new ConcreteState2(); private State CurrentState; public void setCurrentState(State currentState) { CurrentState = currentState; CurrentState.setContext(this); } public void handle1() { this.CurrentState.handle1(); } public void handle2() { this.CurrentState.handle2(); } }
使用状态模式:
Context context = new Context(); context.setCurrentState(new ConcreteState1()); context.handle1(); context.handle2()
优点:很明显的是结构清晰,不同的状态对应不同的对象。程序耦合性低,复杂性低;体现了开闭原则和单一职责原则,每个状态都是一个子类;封装性很好,状态的切换在类内部完成,外部不需要知道状态和行为的切换是如何完成的。
缺点:缺点也很明显,状态越对,对应的类就越多,造成类膨胀。
3.21、解释器模式(Expression Pattern)
解释器模式是一种比较少用的模式。给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言的句子。
抽象解释器:AbstractExpression
具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成。
终结符表达式:TerminalExpression
通常解释器模式中只有一个终结符表达式,但可以有多个实例,对应不同的终结符。
非终结符表达式:NonterminalExpression
每条规则对应一个非终结表达式。
抽象表达式:
public abstract class Expression { public abstract Object interpreter(Context ctx); }
终结符表达式:
public class TerminalExpression extends Expression { @Override public Object interpreter(Context ctx) { return null; } }
非终结符表达式:
public class NonterminalExpression extends Expression { public NonterminalExpression(Expression... expressions) { } @Override public Object interpreter(Context ctx) { return null; } }
Context,环境角色:
public class Context { //通常是一个容器 }
优点:简单的语法分析工具,显著优点是扩展性强,修改语法规则只需要修改相应的非终结符表达式即可;扩展语法只要增加非终结符类就可以了。
缺点:会引起类膨胀,语法规则复杂的时候,会产生打开的类。解释器模式采用递归调用的方式。效率较低,冗长的语法解释较慢。
3.22、享元模式(FlyWeight Pattern)
享元模式是池技术的重要实现方式,其定义如下:使用共享对象可以有效地支持大量的细粒度的对象。两个要求:细粒度的对象和共享对象。将对象的信息分为两个部分:内部状态和外部状态。(对象池注重对象的复用,享元注重对象的共享)
内部状态:
内部状态是对象可共享出来的信息,存储在享元对象内部且不会随着环境的改变而改变;它们可以作为一个对象的动态附加信息,不必直接存储在具体的对象中,属于可以共享的部分。具体用的时候在去变更,写。
外部状态:
外部状态是对象得以依赖的一部分,是随着环境的改变而改变,不可以共享的状态。它是一批对象的同一标志,是唯一的索引值。
抽象享元角色:
public abstract class FlyWeight { private String intrinsic; protected final String Extrinsic; public FlyWeight(String extrinsic) { Extrinsic = extrinsic; } public abstract void operate(); public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; } }
具体享元角色:
public class ConcreteFlyWeight1 extends FlyWeight { public ConcreteFlyWeight1(String extrinsic) { super(extrinsic); } @Override public void operate() { // } } public class ConcreteFlyWeight2 extends FlyWeight { public ConcreteFlyWeight2(String extrinsic) { super(extrinsic); } @Override public void operate() { // } }
享元工厂,内含对象池:
public class FlyWeightFactory{ private static HashMap<String, FlyWeight> pool = new HashMap<>(); public static FlyWeight getFlyWeight(String Extrinsic) { FlyWeight flyWeight = null; if (pool.containsKey(Extrinsic)) { flyWeight = pool.get(Extrinsic); } else { flyWeight = new ConcreteFlyWeight1(Extrinsic); pool.put(Extrinsic, flyWeight); } return flyWeight; } }
优点:非常简单的模式,大大减少了应用程序创建的对象数量。降低程序内存的占用,增强程序的性能。
缺点:提高了系统的复杂性,需要剥离出外部状态和内部状态,而且外部具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
3.23、桥梁模式(Bridge Pattern)
桥梁模式也叫桥接模式,比较简单明了的模式。定义:将抽象和实现解耦,使得两者可以独立地变化。
实现化角色:是接口或者抽象类,定义角色的行为和属性。
//实现化角色 public interface Implementor { public void doSomething(); public void doAnything(); }
具体的实现化角色,实现了抽象化角色的类,实现接口或抽象类中定义的方法和属性。
public class ConcreteImplementor1 implements Implementor{ @Override public void doSomething() { } @Override public void doAnything() { } } public class ConcreteImplementor2 implements Implementor{ @Override public void doSomething() { } @Override public void doAnything() { } }
抽象化角色,主要职责是定义出该角色的行为,同时保存一个队实现化角色的引用,该角色一般都是抽象类。
//抽象化角色 public abstract class Abstraction{ private Implementor imp; public Abstraction(Implementor imp) { this.imp = imp; } public void request(){ imp.doSomething(); } public Implementor getImp() { return imp; } }
具体抽象化角色,继承抽象化角色的实体类,引用实现化角色对抽象化角色进行引用。
//具体的抽象化角色 public class RefinedAbstraction extends Abstraction{ public RefinedAbstraction(Implementor imp) { super(imp); } @Override public void request() { super.request(); super.getImp().doAnything(); } }
使用桥梁模式,连接抽象角色和实现化角色。
Implementor implementor = new ConcreteImplementor1(); Abstraction refinedAbstraction = new RefinedAbstraction(implementor); refinedAbstraction.request()
优点:1:可以解决继承的缺点
2:实现对客户透明
3:提高灵活性和扩展性