-
2019-07-31 19:13:12
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,是可复用面向对象软件的基础。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。java设计模式广泛认同的有23种,本文介绍最常用的、最会被问到的设计模式。
#一、设计模式的分类
总体来说23种设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
#二、设计模式的六大原则
总原则-开闭原则
对扩展开放,对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
1、单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
尽量首先使用合成/聚合的方式,而不是使用继承。
#常用设计模式
##1、工厂方法模式
工厂方法模式分为三种:
1.1、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
举例如下:(我们举一个发送邮件和短信的例子)public interface Sender { public void Send(); } public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mailsender!"); } } public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); } } public class SendFactory { public Sender produce(String type) { if ("mail".equals(type)) { return new MailSender(); } else if ("sms".equals(type)) { return new SmsSender(); } else { System.out.println("请输入正确的类型!"); return null; } } } public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produce("sms"); sender.Send(); } }
输出:this is sms sender!
2.2、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
将上面的代码做下修改,改动下SendFactory类就行,如下:public class SendFactory { public Sender produceMail(){ return new MailSender(); } public Sender produceSms(){ return new SmsSender(); } } public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produceMail(); sender.Send(); } }
输出:this is mailsender!
3.3、静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。public class SendFactory { public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } public class FactoryTest { public static void main(String[] args) { Sender sender = SendFactory.produceMail(); sender.Send(); } }
输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
##2、抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。public interface Sender { public void Send(); } public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mailsender!"); } } public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); } } public class SendMailFactory implements Provider { @Override public Sender produce(){ return new MailSender(); } } public class SendSmsFactory implements Provider{ @Override public Sender produce() { return new SmsSender(); } } public interface Provider { public Sender produce(); } public class Test { public static void main(String[] args) { Provider provider = new SendMailFactory(); Sender sender = provider.produce(); sender.Send(); } }
其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
##3、单例模式
java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍经常被面试的经典的两种:懒汉模式、饿汉模式。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。比如每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
###3.1、懒汉式单例//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { private Singleton() {} private static Singleton single=null; //静态工厂方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。
1、在getInstance方法上加同步public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
2、双重检查锁定
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
3、静态内部类
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
这种方式是当被调用getInstance()时才去加载静态内部类LazyHolder,LazyHolder在加载过程中会实例化一个静态的Singleton,因为利用了classloader的机制来保证初始化instance时只有一个线程,所以Singleton肯定只有一个,是线程安全的,这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
###3.2、饿汉式单例
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
###3.3、饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
综上大部分情况下单例模式中最好的实现方式就是静态内部类的懒汉模式。
##4、建造者模式
一、场景
当需要生产一辆汽车时,我们需要为其装配发动机、轮胎、座椅等等部件,这个装配过程是比较复杂的而且也需要较高的组装技术。而建造者模式(Builder Pattern)就是为了将部件与组装分离开。
二、 概念
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。
建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。
三、建造者模式结构组成
Product: 表示被构造的复杂对象,其中包含需要构建的部件属性。
Builder: 创建一个产品对象的各个部件指定抽象接口。
ConcreteBuilder: 实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示。
Director: 调用具体建造者角色以创建产品对象。
下面以构建一辆汽车为例,写示例代码:
1.Product角色:组装一辆汽车首先的有各种配件,如发动机、轮胎、座椅等。public class Car{ public String engine; public String tyre; public String seat; public Car(){ } public String getEngine() { return engine; } public void setEngine(String engine) { this.engine = engine; } public String getTyre() { return tyre; } public void setTyre(String tyre) { this.tyre = tyre; } public String getSeat() { return seat; } public void setSeat(String seat) { this.seat = seat; } }
2.Builder角色:知道了所需配件后,就需要生产配件了,定义一个生产配件的抽象建造者接口。
public interface Builder { String buildEngine(); String buildTyre(); String buildSeat(); }
3.ConcreteBuilder角色:实现抽象的 建造者接口生成具体的建造者,并开始生产具体的配件。
public class CarBuilder implements Builder{ @Override public String buildEngine() { // 生产发动机 return "发动机"; } @Override public String buildTyre() { // 生产轮胎 return "轮胎"; } @Override public String buildSeat() { // 生产座椅 return "座椅"; } } }
4.Director角色:在生产出配件后,由指导者指导组装配件生成汽车。
public class CarDirector { CarBuilder cb; public CarDirector(CarBuilder cb){ this.cb=cb; } public Car constructCar(){ Car car=new Car(); car.setEngine(cb.buildEngine()); car.setTyre(cb.buildTyre()); car.setSeat(cb.buildSeat()); return car; } }
5.最终得到一辆汽车:
public class Client { public static void main(String[] args) { CarDirector carDirector=new CarDirector(new CarBuilder()); Car car=carDirector.constructCar(); System.out.println(car.getEngine()+car.getTyre()+car.getSeat()); } }
##5、适配器模式
适配器模式,把一个类接口变化成客户端所期待的另一个类的接口,使原来因接口不匹配而无法一起工作的类能够一起工作。Java源码中的例子:如Java IO中的java.io.InputStreamReader(InputStream) 和java.io.OutputStreamWriter(OutputStream)就是典型的适配器模式,通过InputStreamReader、OutputStreamWriter适配器将字节流转换为字符流。
###5.1、类适配器
Target(目标角色): 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
Adaptee(源角色):现在需要适配的类。
Adapter(适配器): 适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。/** * 目标角色,如举例中需要转换成的三相插头 * @author 14501_000 */ public interface Target { void handleReq(); } /** * 源角色,需要被适配的类,如举例中的两脚插头 */ public class Adaptee { public void request(){ System.out.println( "可以完成客户请求的需要的功能!" ); } } /** * 适配器,把源接口转换成目标接口,即将两脚插头转换为三脚插头 * */ public class Adapter extends Adaptee implements Target{ public void handleReq() { super.request(); } } /** * 客户端类,通过三脚插座进行工作 * */ public class Client { public void work(Target t){ t.handleReq(); } public static void main(String[] args){ Client c = new Client(); Target t = new Adapter(); c.work(t); } }
###5.2、对象适配器
上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 Adaptee (被适配类),也实现了 Target 接口(因为 Java 不支持多继承,所以这样来实现),在 Client 类中我们可以根据需要选择并创建任一种符合需求的子类,来实现具体功能。另外一种适配器模式是对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式,通过组合的方式跟适配对象组合。/** * 适配器,把源接口转换成目标接口,即将两脚插头转换为三脚插头 * */ public class Adapter implements Target{ Adaptee adaptee ; public Adapter(Adaptee adaptee){ this.adaptee = adaptee ; } public void handleReq() { adaptee.request(); } } public class Client { public void work(Target t){ t.handleReq(); } public static void main(String[] args ) { Client c =new Client(); Adaptee adaptee =new Adaptee(); Target t = new Adapter(adaptee); c.work( t ); } }
测试结果与上面的一致。 使用对象适配器模式,可以使得 Adapter 类(适配类)根据传入的 Adaptee 对象达到适配多个不同被适配类的功能,当然,此时我们可以为多个被适配类提取出一个接口或抽象类。这样看起来的话,似乎对象适配器模式更加灵活一点。
###5.3、适配器模式适用场景
系统需要使用现有的类,而这些类的接口不符合系统的接口。
想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
两个类所做的事情相同或相似,但是具有不同接口的时候。
旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
##6、装饰者模式
装饰(Decorate)模式又称为包装(Wrapper)模式。可以动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
举一个简单的汽车例子,创造每一种功能的汽车都需要继承车的父类进行实现,那么当我们需要既能路上行驶又能水上行驶的车又得继续继承父类拓展新的类。所以每增加一种新功能的汽车都需要新增一个类,这样的话就会创建大量的类。这时候就能使用装饰模式了。
###6.1、示例
抽象构件public interface AbstractCar { void move(); }
具体构建
public class Car implements AbstractCar{ public void move() { System.out.println("路上行驶"); } }
装饰角色
public class SuperCar implements AbstractCar{ protected AbstractCar car; public SuperCar(AbstractCar car){ this.car=car; } public void move() { car.move(); } }
具体装饰 角色
/** * 飞行汽车 */ ublic class FlyCar extends SuperCar{ public FlyCar(AbstractCar car) { super(car); } public void fly() { System.out.println("空中行驶汽车"); } @Override public void move() { super.move(); fly(); } } /** * 水上汽车 */ public class WaterCar extends SuperCar{ public WaterCar(AbstractCar car) { super(car); } public void swim() { System.out.println("水上行驶汽车"); } @Override public void move() { super.move(); swim(); } }
客户端
public class Client { public static void main(String[] args) { Car car=new Car(); car.move(); System.out.println("------增加新功能,飞行------"); FlyCar flyCar=new FlyCar(car); flyCar.move(); System.out.println("------新增加功能,水上行驶-----"); WaterCar waterCar=new WaterCar(car); waterCar.move(); System.out.println("-----新增加两个功能,飞行与水上行驶-----"); WaterCar waterCar2=new WaterCar(flyCar); waterCar2.move(); } }
//输出结果
路上行驶
------增加新功能,飞行------
路上行驶
空中行驶汽车
------新增加功能,水上行驶-----
路上行驶
水上行驶汽车
-----新增加两个功能,飞行与水上行驶-----
路上行驶
空中行驶汽车
水上行驶汽车
由此可知,使用装饰模式就不用创建大量新的类而可以拓展出具有更多功能的对象了。
###6.2、装饰模式在Java I/O库中的应用
IO流实现细节:
Component抽象构件角色:io流中的InputStream,OutputStream,Reader,Writer
ConcreteComponent具体构件角色:io流中的FileInputStream,FileOutputStream
Decorate装饰角色:持有抽象构件的引用,FilterInputStream,FilterOutputStream
ConcreteDecorate具体装饰角色:负责给构件对象添加新的责任,BufferedInputStream,BufferedOutputStream等
##7、代理模式
为某个对象提供一个代理,从而控制这个代理的访问。代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代。代理类负责请求的预处理、过滤、将请求分配给委托类处理、以及委托类处理完请求的后续处理。
###7.1、静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
示例代码:
1.抽象角色public interface AbstractSubject { void doSomething(); }
2.代理角色
public class ProxySubject implements AbstractSubject{ private AbstractSubject real ; public ProxySubject(AbstractSubject real) { this.real=real ; } @Override public void doSomething() { real.doSomething(); } public void doOtherthing() { } }
3.真实角色
public class RealSubject implements AbstractSubject{ @Override public void doSomething() { System.out.println( "真实角色被使用" ); } }
4.客户端
public class Client { public static void main(String[] args ) { RealSubject real = new RealSubject(); ProxySubject proxy = new ProxySubject( real ); proxy.doSomething(); } }
5.静态代理的优缺点
优点: 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
###7.2、动态代理
动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件。代理角色和真实角色的联系在程序运行时确定。
1.首先看看和动态代理相关JavaAPI
① .java.lang.reflect.Proxy
这是Java动态代理机制生成的所有代理类的父类,它提供了一组静态的方法来为一组接口动态的生成代理类及其对象。
Proxy类的静态方法://方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy ) //方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader,Class[] interfaces) //方法 3:该方法用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl ) //方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h )
②.java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
InvocationHandler核心方法//该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象 //第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 Object invoke(Object proxy, Method method,Object[] args )
③ .java.lang.reflect.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象 。
2.动态代理实现步骤- 实现InvocationHandler接口创建自己的调用处理器 。
- 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 。
- 执行真实角色具体任务。
示例代码
1.抽象角色和真实角色代码与上述相同 。
2. 创建自己的调用处理器:public class SubjectHandler implements InvocationHandler{ AbstractSubject real; public SubjectHandler(AbstractSubject real){ this.real=real; } @Override public Object invoke(Object obj, Method method, Object[] args)throws Throwable { System.out.println("代理类预处理任务"); //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。 //因为示例程序没有返回值,所以这里忽略了返回值处理 method.invoke(real, args); System.out.println("代理类后续处理任务"); return null; } }
3.客户端 :
public class Client { public static void main(String[] args) { RealSubject real=new RealSubject(); SubjectHandler handler=new SubjectHandler(real); //生成代理类对象 AbstractSubject proxy=(AbstractSubject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{AbstractSubject.class},handler); proxy.doSomething(); } }
4.动态代理的优缺点
优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
不足:
Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
##8、策略模式
下面是某个市场人员接到单后的报价策略,报价策略很复杂,可以简单作如下分类:
普通客户小批量,不打折
普通客户大 批量,打9折
老 客户小批量,打8.5折
老 客户大批量,打8折
我们通常可以适用条件语句进行处理,代码如下:public class Test { public double getPrice(String type ,double price ){ if(type.equals("普通客户小批量")){ System.out.println("不打折"); return price; }else if(type.equals("普通客户大批量")){ System.out.println("打9折"); return price*0.9; }else if(type.equals("老客户小批量")){ System.out.println("打8.5折"); return price*0.85; }else if(type.equals("老客户大批量")){ System.out.println("打8折"); return price*0.8; } return price; } }
这样实现起来比较简单,符合一般开人员的思路,但是当类型特别多,算法比较复杂时,整个条件控制代码会变得很长,难于维护。这时我们可以采用策略模式,将不同的策略分开,委派给不同的对象管理。
使用策略模式实现:public interface Strategy { /** * * @param price 商品原价 * @return 打折后价格 */ public double getPrice(double price); } public class NewCustomerFewStrategy implements Strategy{ public double getPrice(double price) { System.out.println("普通客户小批量,不打折"); return price; } } public class NewCustomerManyStrategy implements Strategy{ public double getPrice(double price) { System.out.println("普通客户大批量,打9折"); return price*0.9; } } public class OldCustomerFewStrategy implements Strategy{ public double getPrice(double price) { System.out.println("老客户小批量,打8.5折"); return price*0.85; } } public class OldCustomerManyStrategy implements Strategy{ public double getPrice(double price) { System.out.println("老客户大批量,打8折"); return price; } } public class Context { private Strategy strategy;//持有策略引用 public Context(Strategy strategy) { super(); this.strategy = strategy; } public void printPrice(double price ){ System.out.println("价格为:"+strategy.getPrice(price)); } } public class Client { public static void main(String[] args) { Strategy strategy= new NewCustomerFewStrategy(); Context context= new Context(strategy); context.printPrice(100); } } //输出结果 //普通客户小批量,不打折 //价格为:100.0
从上面的示例可以看出,策略模式仅仅封装算法,提供新的算法插入到已有系统中,以及老算法从系统中“退休”的方法,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。
优点:
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
##9、模板方法模式
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
比如定义一个操作中的算法的骨架,将步骤延迟到子类中。模板方法使得子类能够不去改变一个算法的结构即可重定义算法的某些特定步骤。
模式中的角色:- 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
- 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。
以准备去学校所要做的工作(prepareGotoSchool)为例,假设需要分三步:穿衣服(dressUp),吃早饭(eatBreakfast),带上东西(takeThings)。学生和老师要做得具体事情肯定有所区别。
抽象类AbstractClasspublic abstract class AbstractPerson{ //抽象类定义整个流程骨架 public void prepareGotoSchool(){ dressUp(); eatBreakfast(); takeThings(); } //以下是不同子类根据自身特性完成的具体步骤 protected abstract void dressUp(); protected abstract void eatBreakfast(); protected abstract void takeThings(); }
具体类ConcreteClass
public class Student extends AbstractPerson{ @Override protected void dressUp() { System.out.println(“穿校服"); } @Override protected void eatBreakfast() { System.out.println(“吃妈妈做好的早饭"); } @Override protected void takeThings() { System.out.println(“背书包,带上家庭作业和红领巾"); } }
public class Teacher extends AbstractPerson{ @Override protected void dressUp() { System.out.println(“穿工作服"); } @Override protected void eatBreakfast() { System.out.println(“做早饭,照顾孩子吃早饭"); } @Override protected void takeThings() { System.out.println(“带上昨晚准备的考卷"); } }
public class Client { public static void main(String[] args) { Student student = new Student() student.prepareGotoSchool(); Teacher teacher = new Teacher() teacher.prepareGotoSchool(); } }
优点:
模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
子类实现算法的某些细节,有助于算法的扩展。
通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
缺点:
每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
适用场景:
在某些类的算法中,用了相同的方法,造成代码的重复。
控制子类扩展,子类必须遵守算法规则。
##10、观察者模式
观察者模式定义了一个一对多的依赖关系,让多个观察者对象同时监听同一个主题对象。当这个主题状态发生改变时,会通知所有观察者对象,让它们自动更新自己。
###10.1、模型结构- 抽象主题角色(Subject): 把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
- 具体主题角色(ConcreteSubject): 在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
- 抽象观察者角色(Observer): 为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
- 具体观察者角色(ConcreteObserver): 该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
###10.2、代码示例
抽象主题角色:public class Subject { protected List<Observer> list=new ArrayList<Observer>(); public void registerObserve(Observer obs){ list.add(obs); } public void removeObserve(Observer obs){ list.remove(obs); } //通知所有的观察者更新状态 public void notifyAllObserve(){ for(Observer obs:list){ obs.update(this); } } }
具体 主题角色:
public class ConcreteSubject extends Subject{ private int state; public int getState() { return state; } //主题对象发生变化,通知所有观察者 public void setState(int state) { this.state = state; this.notifyAllObserve(); } }
抽象观察者角色:
public interface Observer { void update(Subject sub); }
具体观察者角色:
public class ConcreteObserver implements Observer{ private int myState;//与目标对象state值保持一致 public void update(Subject sub) { myState=((ConcreteSubject)sub).getState(); System.out.println("观察者得到的值:"+myState); } }
客户端 :
public class Client { public static void main(String[] args) { //目标对象 ConcreteSubject cs=new ConcreteSubject(); //创建多个具体观察者 ConcreteObserver observe1=new ConcreteObserver(); ConcreteObserver observe2=new ConcreteObserver(); ConcreteObserver observe3=new ConcreteObserver(); //注册观察者 cs.registerObserve(observe1); cs.registerObserve(observe2); cs.registerObserve(observe3); //改变被观察者状态 cs.setState(2); } }
输出结果
观察者得到的值:2
观察者得到的值:2
观察者得到的值:2
###10.3、推模式与拉模式
推模式:每次都会把通知以广播的方式发送给所有观察者,所有观察者只能被动接收, 推送的信息通常是主题对象的全部或部分数据 。
拉模式: 主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了 。
比较: 推模式是假定主题对象知道观察者需要的数据;而拉模式是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
###10.4、Java自带对观察者模式的支持
JavaSE中提供了java.util.Observable和java.util.Observer来实现观察者模式。
代码示例:
具体目标对象:public class ConcreteSubject extends Observable{ private int state; public int getState() { return state; } public void setState(int state) { this.state = state;//目标对象状态发生改变 setChanged();//表示目标对象已经做了更改 notifyObservers(state);//通知所有观察者 } } 1 2 3 4
具体观察者:
public class ConcreteObserver implements Observer{ private int mystate; public void update(Observable o, Object arg) { mystate=((ConcreteSubject)o).getState(); System.out.println("观察者接收到的状态:"+mystate); } }
客户端:
public class Client { public static void main(String[] args) { //创建具体主题 ConcreteSubject cs=new ConcreteSubject(); //创建观察者 ConcreteObserver observer1=new ConcreteObserver(); ConcreteObserver observer2=new ConcreteObserver(); ConcreteObserver observer3=new ConcreteObserver(); //将观察者加入到目标对象观察者集合 cs.addObserver(observer1); cs.addObserver(observer2); cs.addObserver(observer3); //目标对象改变 cs.setState(100); } }
输出结果:
观察者接收到的状态:100
观察者接收到的状态:100
观察者接收到的状态:100
上面的内容借鉴了很多博主的文章
更多相关内容 -
常用设计模式
2020-06-29 21:41:01设计模式 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联和组合关系的充分理解。 正确使用...设计模式
从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联和组合关系的充分理解。
正确使用设计模式具有一下优点:
1、可以提高程序员的思维能力、编码能力和设计能力。
2、使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
3、使设计的代码可重用性高、可读性强、可靠行高、灵活性好、可维护性强。
1、3大类
创建性模式:
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式,代理模式
行为型模式:
模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、接解释器模式、状态模式、策略模式、职责链模式、访问者模式。
2、OOP七大法则
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立。
- 依赖倒置原则:要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:要为各个类建立他们需要专用接口。
- 迪米特法则:只与你的朋友直接交谈,不跟陌生人说话。
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
3、单例模式
按照惯有的分类方式,设计模式总共分为3大类:1、创建型 2、结构型 3、行为型。
单例模式便是创建型设计模式的一种,它确保某一个类在系统中只有一个实例,并自行实例化,同时向外部提供获取这个唯一实例的接口。从这段描述中,我们不难可以得到单例模式的三大特性:
单例类只有一个实例。
单例类必须自己实例化自己。
单例类需要向外提供实例。3.1、饿汉式
/** * 上面是饿汉式单例模式的标准代码, * EagerSingleton类的实例因为变量instance申明为static的关系, * 在类加载过程中便会执行。由此带来的好处是Java的类加载机制本身为我们保证了实例化过程的线程安全性, * 缺点是这种空间换时间的方式,即使类实例本身还未用到,实例也会被创建。 * */ class EagerSingleton { // 静态变量,类在创建之初就会执行实例化动作。 private static EagerSingleton instance = new EagerSingleton(); // 私有化构造函数,使外界无法创建实例 private EagerSingleton(){} // 为外界提供获取实例接口 public static EagerSingleton getInstance(){ return instance; } }
3.2、懒汉式
/** * 懒汉式 * 这个缺点的原因,涉及到并发编程的原子性。 * 实例中,创建实例的代码逻辑失去了原子性从而导致可能存在多个实例创建的情况。 * */ public class LazySingleton { private static LazySingleton instance = null; private LazySingleton(){ System.out.println(Thread.currentThread().getName()+"ok"); } // 为外界提供获取实例接口 public static LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); // 懒加载 } return instance; } /** * 多线程并发 * 发生了多次实例化对象 * */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazySingleton.getInstance(); }).start(); } } }
3.3、懒汉式加锁
/** * 双重检测锁 * */ class Singleton2 { //volatile 原子性操作 private volatile static Singleton2 instance = null; /** * 设置标志位 * */ private static boolean youyuan = false; private Singleton2(){ synchronized (Singleton2.class){ if (youyuan == false){ youyuan = true; }else { throw new RuntimeException("不要试图使用反射破坏异常"); } } System.out.println(Thread.currentThread().getName()+"ok"); } public static Singleton2 getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized (Singleton2.class) { //再次检查实例是否存在,如果不存在才真正的创建实例 if(instance == null){ instance = new Singleton2();//不是一个原子性操作 } } } return instance; } /** * 线程安全 * */ // public static void main(String[] args) { // for (int i = 0; i < 10; i++) { // new Thread(()->{ // Singleton2.getInstance(); // }).start(); // } // } //可以通过反射破坏 public static void main(String[] args) throws Exception{ //Singleton2 instance = Singleton2.getInstance(); Constructor<Singleton2> declaredConstructor = Singleton2.class.getDeclaredConstructor(); Field youyuan = Singleton2.class.getDeclaredField("youyuan"); youyuan.setAccessible(true); declaredConstructor.setAccessible(true); Singleton2 singleton2 = declaredConstructor.newInstance(); //修改字段 youyuan.set(singleton2,false); Singleton2 singleton21 = declaredConstructor.newInstance(); System.out.println(singleton21); System.out.println(singleton2); } }
3.4、静态内部类
/** * 静态内部类 * */ class Singleton3 { private Singleton3(){ System.out.println(Thread.currentThread().getName()+"ok"); } // 只有当类被调用时,才会加载 private static class SingletonHolder{ // 静态初始化器,由JVM来保证线程安全 private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance(){ return SingletonHolder.instance; } }
3.5、枚举类
/** * 枚举类,本身也是一个类,不能通过反射获取字段 * */ enum Singleton { uniqueInstance; public Singleton singletonOperation(){ // 单例类的其它操作 return uniqueInstance; } public static void main(String[] args) throws Exception{ Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); Singleton singleton = declaredConstructor.newInstance(); System.out.println(singleton); } }
4、工厂模式
实现了创建者和调用者的分离。
4.1、简单工厂模式
用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码)
Car接口
public interface Car { public void carName(); }
Car实现类
public class Wuling implements Car { @Override public void carName() { System.out.println("Wuling"); } }
静态工厂
public class CarFactory { public static Car getCar(String name){ if (name.equals("DaZhong")){ return new DaZhong(); }else if (name.equals("Wuling")){ return new Wuling(); }else{ return null; } } }
测试
public class Main { public static void main(String[] args) { // Wuling wuling = new Wuling(); // wuling.carName(); Car wuling = CarFactory.getCar("Wuling"); wuling.carName(); } }
4.2、工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
4.3、抽象工厂模式
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。
适用场景:
-
客户端不依赖于产品类实例如何被创建、实现等细节。
-
强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码。
-
提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现。
优点
-
具体产品在应用层的代码隔离,无需关心创建的细节。
-
将一个系列的产品统一到一起创建。
4.4、小结
小结
1、简单工厂模式(静态工厂模式):虽然某种程度上不符合设计原则,但实际使用最多。
2、工厂方法模式:不修改已有类的前提上,通过增加新的工厂类实现扩展。
3、抽象工厂模式:不可以增加产品,可以增加产品族。
应用场景
1、JDk中Calendar的getInstance方法
2、JDBC中的Connection对象的获取
3、Spring中IOC容器创建管理bean对象
4、反射中Class对象的newInstance方法
核心本质
-
实例化对象不使用new,用工厂方法替代。
-
将选择实现类,创建对象统一管理和控制,从而将调用者跟我们的实现类解耦。
5、建造者模式
建造者模式也属于创建型模式,他提供了一种创建对象的最佳方式。
定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
主要作用
在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的创建过程和细节隐藏起来)。
例子
工厂(建造者模式):负责制造汽车。
汽车购买者(用户):只需要说出你需要的型号,然后直接购买就可以使用了
5.1、监视者模式
指挥
//指挥:核心。负责指挥构建一个工程,工程如何构建,由它决定 public class Director { //指挥工人按照顺序建房子 public Product build(Builder builder){ builder.buildA(); builder.buildB(); builder.buildC(); builder.buildD(); return builder.getProduct(); } }
具体的建造者
//抽象的建造者:方法 public abstract class Builder { abstract void buildA(); //地基 abstract void buildB(); //钢筋水泥 abstract void buildC(); //铺电线 abstract void buildD(); //粉刷 //完工:得到产品 abstract Product getProduct(); } //具体的建造者:工人 public class Worker extends Builder { private Product product; public Worker() { this.product = new Product(); } @Override void buildA() { product.setBuildA("地基"); System.out.println("地基"); } @Override void buildB() { product.setBuildA("钢筋水泥"); System.out.println("钢筋水泥"); } @Override void buildC() { product.setBuildA("铺电线"); System.out.println("铺电线"); } @Override void buildD() { product.setBuildA("粉刷"); System.out.println("粉刷"); } @Override Product getProduct() { return product; } }
产品
package org.javaboy.builder; /** * @Author you猿 * @Date 2020/6/27 12:09 */ //产品:房子 public class Product { private String buildA; //地基 private String buildB; //钢筋水泥 private String buildC; //铺电线 private String buildD; //粉刷 public String getBuildA() { return buildA; } public void setBuildA(String buildA) { this.buildA = buildA; } public String getBuildB() { return buildB; } public void setBuildB(String buildB) { this.buildB = buildB; } public String getBuildC() { return buildC; } public void setBuildC(String buildC) { this.buildC = buildC; } public String getBuildD() { return buildD; } public void setBuildD(String buildD) { this.buildD = buildD; } @Override public String toString() { return "Product{" + "buildA='" + buildA + '\'' + ", buildB='" + buildB + '\'' + ", buildC='" + buildC + '\'' + ", buildD='" + buildD + '\'' + '}'; } }
测试
public class Test { public static void main(String[] args) { //指挥 Director director = new Director(); //指挥 具体的工人完成 产品 Product build = director.build(new Worker()); System.out.println(build.toString()); } }
5.2、匿名模式
建造者
//建造者 public abstract class Builder { abstract Builder builderA(String msg); //汉堡 abstract Builder builderB(String msg); //可乐 abstract Builder builderC(String msg); //薯条 abstract Builder builderD(String msg); //甜点 abstract Product getProduct(); } public class Worker extends Builder { private Product product; public Worker( ) { this.product = new Product(); } @Override Builder builderA(String msg) { product.setBuilderA(msg); return this; } @Override Builder builderB(String msg) { product.setBuilderB(msg); return this; } @Override Builder builderC(String msg) { product.setBuilderC(msg); return this; } @Override Builder builderD(String msg) { product.setBuilderD(msg); return this; } @Override Product getProduct() { return product; } }
产品
package org.javaboy.builder.demo2; /** * @Author you猿 * @Date 2020/6/27 20:13 */ public class Product { private String builderA = "汉堡"; private String builderB = "可乐"; private String builderC = "薯条"; private String builderD = "甜点"; public String getBuilderA() { return builderA; } public void setBuilderA(String builderA) { this.builderA = builderA; } public String getBuilderB() { return builderB; } public void setBuilderB(String builderB) { this.builderB = builderB; } public String getBuilderC() { return builderC; } public void setBuilderC(String builderC) { this.builderC = builderC; } public String getBuilderD() { return builderD; } public void setBuilderD(String builderD) { this.builderD = builderD; } @Override public String toString() { return "Product{" + "builderA='" + builderA + '\'' + ", builderB='" + builderB + '\'' + ", builderC='" + builderC + '\'' + ", builderD='" + builderD + '\'' + '}'; } }
测试
public class Test { public static void main(String[] args) { //服务员 Worker worker = new Worker(); //链式编程 Product product = worker.builderA("冰淇淋").builderB("烤鸡腿") .getProduct(); System.out.println(product.toString()); } }
5.3、优缺点
优点
- 产品的建造和表示分离,实现了解耦。
- 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
- 具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合开闭原则。
缺点
- 创建的产品一般具有较多的共同点,其组成部分相似:如果产品之间差异性很大,不适用于建造者模式。
5.4、应用场景
应用场景:
-
需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
-
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
-
适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
建造者与抽象工厂模式的比较:
-
与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
-
在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于-步步构造一 个复杂对象,返回-一个完整的对象。
-
如果将抽象工厂模式看成汽车配件生产工厂,生产-一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回-辆完整的汽车!
6、原型模式
Prototype、Cloneable接口、clone()方法、 克隆
6.1、浅克隆
1、实体类
/** * 1、实现一个接口 Cloneable * 2、重写一个方法 * */ //video public class Video implements Cloneable { private String name; private Date createTime; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public Video(String name, Date createTime) { this.name = name; this.createTime = createTime; } public Video() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", createTime=" + createTime + '}'; } }
2、克隆
public static void main(String[] args) throws CloneNotSupportedException { //原型对象 v1 Date date = new Date(); Video v1 = new Video("youyuan", date); //v1 克隆 v2 Video v2 = (Video) v1.clone();//克隆出来的对象和原来是一模一样的 System.out.println("v1="+v1); System.out.println("v2="+v2); System.out.println("========================="); date.setTime(12435); System.out.println("v1="+v1); System.out.println("v2="+v2); }
6.2、深克隆
- 序列化与反序列化
- 改造clone()方法
1、实体类
/** * 1、实现一个接口 Cloneable * 2、重写一个方法 * */ //video public class Video implements Cloneable { private String name; private Date createTime; @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); //实现深克隆~ Video v = (Video) obj; v.createTime = ((Date) this.createTime.clone()); return obj; } public Video(String name, Date createTime) { this.name = name; this.createTime = createTime; } public Video() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", createTime=" + createTime + '}'; } }
2、克隆
public static void main(String[] args) throws CloneNotSupportedException { //原型对象 v1 Date date = new Date(); Video v1 = new Video("youyuan", date); //v1 克隆 v2 Video v2 = (Video) v1.clone();//克隆出来的对象和原来是一模一样的 System.out.println("v1="+v1); System.out.println("v2="+v2); System.out.println("========================="); date.setTime(12435); System.out.println("v1="+v1); System.out.println("v2="+v2); }
7、配器模式
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!
7.1、角色分析
- 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。(USB接口)
- 需要适配的类:需要适配的类或者适配者类。(网线)
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象。
7.2、对象适配器的优点
- 一个对象适配器可以把多个不同的适配者适配到同一目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,适配者的子类也可通过该适配器进行适配。
7.3、类适配器缺点
- 一次最多只能适配一个适配者类,不能同时适配多个适配者。
- 类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
7.4、适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可以在将来引进的类一起工作。
package org.javaboy.adapter; /** * @Author you猿 * @Date 2020/5/21 18:43 */ //1. 继承(类适配器,单继承) //2. 组合(对象适配器:常用) //真正的适配者,需要连接USB,连接网线 public class Adapter implements NetToUsb { //extend Adaptee 类适配器 //网线 private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void handleRequest() { //super.connectInternet(); this.adaptee.connectInternet(); } public static void main(String[] args) { // Computer computer = new Computer(); //电脑 // Adaptee adaptee = new Adaptee(); //网线 // Adapter adapter = new Adapter(); //转换接口 // computer.net(adapter); Computer computer = new Computer(); //电脑 Adaptee adaptee = new Adaptee(); //网线 Adapter adapter = new Adapter(adaptee); computer.net(adapter); } } //网线,要被适配的类。 class Adaptee { public void connectInternet(){ System.out.println("连接到网络"); } } //客户端类,想上网,插不上网线 class Computer { //电脑需要连接上转接器才可以上网 public void net(NetToUsb netToUsb){ //上网的实现,找个转接头 netToUsb.handleRequest(); } } //接口转换器的抽象实现 interface NetToUsb{ //作用:处理请求,将网线插入usb public void handleRequest(); }
8、桥接模式
桥接模式是将抽象部分与它的实现分离,使得他们都可以独立地变化。它是一种对象结构型模式,又称为柄提模式或接口模式。
品牌
//品牌 public interface Brand { void info(); } //苹果品牌 public class Apple implements Brand { @Override public void info() { System.out.print("苹果"); } } //联想品牌 public class Lenovo implements Brand { @Override public void info() { System.out.print("联想"); } }
电脑类型
//抽象的电脑类型类 public abstract class Computer { //组合 品牌 protected Brand brand; public Computer(Brand brand) { this.brand = brand; } public void info(){ brand.info();//自带品牌 } } //台式 class Desktop extends Computer{ public Desktop(Brand brand) { super(brand); } @Override public void info() { super.info(); System.out.println("台式机"); } } //苹果 class Laptop extends Computer{ public Laptop(Brand brand) { super(brand); } @Override public void info() { super.info(); System.out.println("笔记本"); } }
组合
public class Test { public static void main(String[] args) { //苹果 笔记本 Computer computer1 = new Laptop(new Apple()); computer1.info(); //联想台式机 Computer computer2 = new Desktop(new Lenovo()); computer2.info(); } }
8.1、最佳实践
-
如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使必们在抽象层建立-个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一 个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
-
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
-
虽然在系统中使用继承是没有问题的, 但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
场景:
1、Java语言通过Java虚拟机实现了平台的无关性。
2、AWT中的Peer架构
3、JDBC驱动程序也是桥接模式的应用之一。
8.2、优缺点
好处分析:
-
桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本。
-
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像- -座桥,可以把两个变化的维度连接起来!
劣势分析:
-
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
-
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
9、静态代理
角色分析:
-
抽象角色:一般使用接口或者抽象类来解决
-
真实角色:被代理的角色
-
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作。
-
客户:访问代理对象的人。
代码步骤
1、接口
//租房 public interface Rent { public void rent(); }
2、真实角色
public class Host implements Rent { @Override public void rent() { System.out.println("房东要出租房子"); } }
3、中介
public class Proxy implements Rent{ private Host host; public Proxy(){ } public Proxy(Host host) { this.host = host; } @Override public void rent() { host.rent(); } //看房 public void seeHouse(){ System.out.println("中介带你看房"); } //签合同 public void heTong(){ System.out.println("签合同"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }
4、客户端访问代理角色
public class Client { public static void main(String[] args) { //房东要租房子 Host host = new Host(); //代理,中介帮房东租房子 Proxy proxy = new Proxy(host); //租房直接找中介 proxy.rent(); } }
代理模式的好处:
- 可以使真实的操作更加纯粹,不用去关注一些公共的业务。
- 公共也就交给代理角色,实现了业务的分工。
- 公共业务发生扩展的时候,方便集中管理。
缺点:
- 一个真实的角色就会产生一个代理角色。代码量会翻倍,开发效率会变低
9.1、AOP实例
AOP的实现
代码:
public interface UserService { public void add(); public void update(); public void delete(); public void query(); } package org.javaboy.staticproxy.demo2; //真实对象 public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("add"); } @Override public void update() { System.out.println("update"); } @Override public void delete() { System.out.println("delete"); } @Override public void query() { System.out.println("query"); } } //代理对象 public class UserServiceProxy implements UserService { private UserServiceImpl userService; public UserServiceProxy(UserServiceImpl userService) { this.userService = userService; } @Override public void add() { log("日志"); System.out.println("add"); } @Override public void update() { System.out.println("update"); } @Override public void delete() { System.out.println("delete"); } @Override public void query() { System.out.println("query"); } //日志方法 public void log(String msg){ System.out.println("使用了"+msg+"方法"); } public void setUserService(UserServiceImpl userService) { this.userService = userService; } }
测试:
public class Client { public static void main(String[] args) { UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl()); userServiceProxy.add(); } }
10、动态代理
-
动态代理和静态代理角色一样
-
动态代理的代理类是动态生成的,不是我们直接写好的。
-
动态代理分为两大类:基于接口的动态代理,基于类的动态代理。
1、基于接口 =》JDK动态代理【使用】
2、基于类:cglib
3、java字节码实现:javasist
需要了解两个类:
- Proxy:代理
- InvocationHandler:调用处理程序。
10.1、租房实例
代码
//房东 public class Host implements Rent { @Override public void rent() { System.out.println("房东要出租房子"); } } //租房 public interface Rent { public void rent(); } //用这个类,自动生成代理类! public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public void setTarget(Object target) { this.target = target; } //生成得到代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //处理代理实例,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现 Object res = method.invoke(target, args); return res; } } //测试 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理角色:未创建 ProxyInvocationHandler handler = new ProxyInvocationHandler(); //通过调用程序处理角色来处理我们要调用的接口对象 handler.setTarget(host); //这里的proxy就是动态生成的,并没有写(面向接口编程) Rent proxy = (Rent) handler.getProxy(); proxy.rent(); } }
封装接口
//用这个类,自动生成代理类! public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public void setTarget(Object target) { this.target = target; } //生成得到代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //处理代理实例,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()); //动态代理的本质,就是使用反射机制实现 Object res = method.invoke(target, args); return res; } } //测试 public class Client { public static void main(String[] args) { //真实角色 UserServiceImpl userService = new UserServiceImpl(); //代理角色,不存在 ProxyInvocationHandler handler = new ProxyInvocationHandler(); //设置代理对象 handler.setTarget(userService); //动态生成代理类 UserService proxy = (UserService) handler.getProxy(); proxy.query(); } }
-
常用的几种设计模式详解
2021-04-09 15:01:27设计模式的概述 设计模式分类 创建型模式 特点是将对象的创建与使用分离(解耦),有 单例、原型、工厂方法、抽象工厂、建造者等5种。 结构型模式 用于描述如何将类或对象按某种布局组成更大的结构,代理、...设计模式的概述
设计模式分类
-
创建型模式
特点是将对象的创建与使用分离(解耦),有 单例、原型、工厂方法、抽象工厂、建造者等5种。
-
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构,代理、适配器、桥接、装饰、享元、组合等7种。
-
行为型模式
用于描述类或对象之间相互协作共同完成 单个对象无法完成的任务,模板方法、策略命令、职责链、状态观察者、中介者、迭代器、访问者、备忘录、解释器等11种。
UML
包含了用例图、类图、对象图、状态图、活动图、时序图、协作图、构建图、部署图等9种。
类图概述
类图显示了模型的静态结构
类的作用
-
简化了人们对系统的理解
-
是系统编码和测试的重要模型
类图表示法
类的表示方式
类与类之间关系表示方式
关联关系
用于表示一类与另一类对象之间的联系,如老师和学生
-
1.单项关联
-
2.双向关联
-
3.自关联
聚合关系
聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:
组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
就是一个类里面有另一个类作参数
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:
实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。
软件设计原则
开闭原则
对扩展开放,对修改关闭。在不修改原有的代码,实现一个热插拔的效果。简言之,是为了更好的扩展。我们可以使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
里氏代换原则
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程(使用接口),这样就降低了客户与实现模块间的耦合。
接口隔离原则
客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上(接口的方法尽量拆分)。
迪米特法则
迪米特法则又叫最少知识原则。
只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
合成复用原则
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 对象间的耦合度低。可以在类的成员位置声明抽象。
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
如下,将继承复用改为聚合复用
修改后
创建者模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
可分为 单例模式、工厂方法模式、抽象工程模式、原型模式、建造者模式。
单例模式
单例模式的结构
单例模式的实现
-
饿汉式
-
懒汉式
-
懒汉式-双重检查锁
/** * 双重检查方式 */ public class Singleton { //私有构造方法 private Singleton() {} private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例 if(instance == null) { synchronized (Singleton.class) { //抢到锁之后再次判断是否为null if(instance == null) { instance = new Singleton(); } } } return instance; } }
-
枚举方式
/** * 枚举方式 */ public enum Singleton { INSTANCE; }
存在问题
- 序列化反序列化破坏
- 通过反射通过单例模式
工厂模式
概述
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严 重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。
如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
工厂模式分三种,简单工厂模式,工厂方法模式,抽象工厂模式。
简单工厂模式
简单工厂不是模式,是一种编程习惯。
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
扩展-静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是
23种设计模式中的。代码如下:
public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { //声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象 Coffee coffee = null; if("american".equals(type)) { coffee = new AmericanCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } else { throw new RuntimeException("对不起,您所点的咖啡没有"); } return coffee; } }
工厂方法模式
概念
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延
迟到其工厂的子类。
结构
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂
方法来创建产品。
-
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法(重写抽象抽象工厂方法),完成具体产品的创建。
-
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同
具体工厂之间一一对应。
实现
优缺点
优点:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,
满足开闭原则;
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
概念
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
结构
-
抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
-
具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多
个抽象产品。
-
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
实现
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级,都是甜品;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以他们是一个二维结构,分别表示产品等级,和产品组。这个案例可以使用抽象工厂模式实现,类图如下:
如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。
优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
使用场景
-
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
-
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
-
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
模式扩展
简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合(spring框架就是用的这个)。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
第一步:定义配置文件
american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee
第二步:改进工厂类
JDK源码解析-Coleection.iterator方法
我们看通过类图看看结构:
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
另:
1,DateForamt类中的getInstance()方法使用的是工厂模式; 2,Calendar类中的getInstance()方法使用的是工厂模式;
原型模式
概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
结构
原型模式包含如下角色:
-
抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
-
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
-
访问类:使用具体原型类中的 clone() 方法来复制新的对象。
实现
原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了
clone()
方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
扩展-深克隆
使用深克隆,可以使用对象流方法。
建造者模式
概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
结构
建造者(Builder)模式包含如下角色:
-
抽象建造者类(AbstractBuilder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
-
具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例(实现原材料生产)。
-
产品类(Product):要创建的复杂对象(原材料)。
-
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建(组装原材料,构成整体)。
类图如下:
实例
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UYnui6Yy-1617951667352)(img/建造者模式1.png)]
优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
创建者模式对比
工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式; 而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
结构型模式
结构型模式分为以下 7 种:
- 代理模式
- 适配器模式
- 装饰者模式
- 桥接模式
- 外观模式
- 组合模式
- 享元模式
代理模式
概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
结构
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类(买票接口): 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 具体主题(Real Subject)类(具体卖票): 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理
【例】火车站卖票
JDK动态代理
jdk动态代理结构和静态代理一样。不同的是静态只有一个proxyPoint代理点(类似于一个代理人),而静态代理将代理点升级为ProxyFactory(类似于一个代理公司),具体代理流程在ProxyFactory里面实现。
使用了动态代理,我们思考下面问题:
ProxyFactory是代理类吗?
ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。
从上面的类中,我们可以看到以下几个信息:
- 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
- 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
动态代理的执行流程是什么样?
执行流程如下:
1. 在测试类中通过代理对象调用sell()方法 2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法 3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法 4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
CGLIB动态代理
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
三种代理的对比
-
jdk代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用 CGLIB代理。
-
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)(集中处理,如在invoke()或intercept()中实现)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能,功能增强;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
使用场景
-
远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节(这个就是RPC思想)。
-
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器(VPN)。
-
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
适配器模式
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式(常用),前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口(SD卡):当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类(TF卡):它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类(TF卡转SD卡):它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器模式
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
对象适配器模式
实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。类图如下:
应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
装饰者模式
定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
装饰(Decorator)模式中的角色:
- 抽象构件(对应案例的FastFood)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(炒饭、炒面)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(装饰者Garnish)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(鸡蛋、培根)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
案例
我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。
类图如下:
这里主要写一下Garnish抽象类,它是继承自FastFood,且聚集了FastFood类
//配料类 public abstract class Garnish extends FastFood { private FastFood fastFood; public FastFood getFastFood() { return fastFood; } public void setFastFood(FastFood fastFood) { this.fastFood = fastFood; } public Garnish(FastFood fastFood, float price, String desc) { super(price,desc); this.fastFood = fastFood; } }
优点
- 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
使用场景
-
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
-
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
小结:
BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
- 不同点:
- 目的不同
装饰者是为了增强目标对象
静态代理是为了保护和隐藏目标对象 - 获取目标对象构建的地方不同
装饰者是由外界传递进来,可以通过构造方法传递
静态代理是在代理类内部创建,以此来隐藏目标对象
- 目的不同
桥接模式
定义:
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式包含以下主要角色:
- 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
案例
【例】视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。
类图如下:
优点:
-
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。
-
实现细节对客户透明
使用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为:
- 模板方法模式
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
模板方法模式
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。(父类中抽象声明,子类重新定义或重写)
结构
模板方法(Template Method)模式包含以下主要角色:
-
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
-
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
-
抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
-
具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
-
钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
-
-
-
具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
实现案例
例】炒菜
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
优缺点
优点:
-
提高代码复用性
-
实现了反向控制
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
使用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制(钩子函数)。
JDK源码应用
InputStream类就使用了模板方法模式。在InputStream类中定义了多个
read()
方法。策略模式
概述
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
案例实现
【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
优缺点
1,优点:
-
策略类之间可以自由切换
-
易于扩展
-
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
2,缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句(可替换大量if else语句)。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
JDK源码应用
Comparator
中的策略模式。在Arrays类中有一个sort()
方法。命令模式
概述
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
结构
- 抽象命令类(Command): 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command):具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(厨师)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(服务员)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
案例实现
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
类图如下:
优缺点
1,优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
2,缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
JDK源码应用
Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
责任链模式
概述
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
结构
职责链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
案例实现
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
类图如下:
优缺点
1,优点:
-
降低了对象之间的耦合度
-
增强了系统的可扩展性
-
增强了给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
-
责任链简化了对象之间的连接
-
责任分担
2,缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
JDK源码应用
在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用
Spring自定义框架
spring回顾
spring使用结构有数据访问层(dao层),到业务逻辑层(service层),到控制层(controller层),到配置文件。我们可以看出:
- userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
- 上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
- UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。
上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)
spring核心功能
Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:
核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:
核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。
- spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
- spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后(非延时加载)会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
- spring-context-support模块是对Spring IoC容器及IoC子容器的扩展支持。
- spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
- spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。
bean的概述
Spring 就是面向
Bean
的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。spring中bean用于对一个类进行封装。如下面的配置:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
为什么Bean如此重要呢?
- spring 将bean对象交由一个叫IOC容器进行管理。
- bean对象之间的依赖关系在配置文件中体现,并由spring完成。
spring ioc相关接口分析
BeanFactory解析
Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂(简单工厂+配置文件),即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。
其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。
那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,
- ListableBeanFactory接口表示这些Bean可列表化(通过列表存储)。
- HierarchicalBeanFactory表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean
- AutowireCapableBeanFactory 接口定义Bean的自动装配规则。
这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; //根据bean的名称获取IOC容器中的的bean对象 Object getBean(String name) throws BeansException; //根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了 <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); //判断容器中是否包含指定名称的bean对象 boolean containsBean(String name); //根据bean的名称判断是否是单例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:
- ClasspathXmlApplicationContext : 根据类路径加载xml配置文件,并创建IOC容器对象。
- FileSystemXmlApplicationContext :根据系统路径加载xml配置文件,并创建IOC容器对象。
- AnnotationConfigApplicationContext :加载注解类配置,并创建IOC容器。
BeanDefinition解析
Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition来描述的(封装配置文件的对象),如下面配置文件
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean> bean标签还有很多属性: scope、init-method、destory-method等。
其继承体系如下图所示。
看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
public interface BeanDefinitionReader { //获取BeanDefinitionRegistry注册器对象 BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); BeanNameGenerator getBeanNameGenerator(); /* 下面的loadBeanDefinitions都是加载bean定义,从指定的资源中 */ int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
BeanDefinitionReader解析
Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成对Bean的解析,看看Spring中BeanDefinitionReader的类结构图,如下图所示。
看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
public interface BeanDefinitionReader { //获取BeanDefinitionRegistry注册器对象 BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); BeanNameGenerator getBeanNameGenerator(); /* 下面的loadBeanDefinitions都是加载bean定义,从指定的资源中 */ int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
BeanDefinitionRegistry解析
BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。
public interface BeanDefinitionRegistry extends AliasRegistry { //往注册表中注册bean void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException; //从注册表中删除指定名称的bean void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; //获取注册表中指定名称的bean BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; //判断注册表中是否已经注册了指定名称的bean boolean containsBeanDefinition(String beanName); //获取注册表中所有的bean的名称 String[] getBeanDefinitionNames(); int getBeanDefinitionCount(); boolean isBeanNameInUse(String beanName); }
继承结构图如下:
从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:
-
DefaultListableBeanFactory
在该类中定义了如下代码,就是用来注册bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
-
SimpleBeanDefinitionRegistry
在该类中定义了如下代码,就是用来注册bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
SpringIOC总结
使用到的设计模式
- 工厂模式。这个使用工厂模式 + 配置文件的方式。
- 单例模式。Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
- 模板方法模式。AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关。
- 迭代器模式。对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式。
spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。
说明
此笔记是我学习黑马程序员课程做的笔记,方便以后查阅。
-
-
unity-23种常见设计模式unity版
2017-10-12 11:50:54总体来说设计模式分为三大类: 创建型模式:共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式:共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元... -
Java常见设计模式总结
2021-09-18 17:18:54设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于人于系统都...总体来说,设计模式分为三大类:5种创建型模式、7种结构型模式、11种行为型模式一、设计模式总述:
1、什么是设计模式:
设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。总体来说,设计模式分为三大类:
- 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式
- 行为型模式:共11种:策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式
其实还有两类:并发型模式和线程池模式,用一个图片来整体描述一下:
2、设计模式的六大原则:
(1)开闭原则 (Open Close Principle) :
开闭原则指的是对扩展开放,对修改关闭。在对程序进行扩展的时候,不能去修改原有的代码,想要达到这样的效果,我们就需要使用接口或者抽象类
(2)依赖倒转原则 (Dependence Inversion Principle):
依赖倒置原则是开闭原则的基础,指的是针对接口编程,依赖于抽象而不依赖于具体
(3)里氏替换原则 (Liskov Substitution Principle) :
里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基础类上增加新的行为。所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现。
里氏替换原则是对 “开闭原则” 的补充,实现 “开闭原则” 的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
(4)接口隔离原则 (Interface Segregation Principle):
使用多个隔离的接口,比使用单个接口要好,降低接口之间的耦合度与依赖,方便升级和维护方便
(5)迪米特原则 (Demeter Principle):
迪米特原则,也叫最少知道原则,指的是一个类应当尽量减少与其他实体进行相互作用,使得系统功能模块相对独立,降低耦合关系。该原则的初衷是降低类的耦合,虽然可以避免与非直接的类通信,但是要通信,就必然会通过一个“中介”来发生关系,过分的使用迪米特原则,会产生大量的中介和传递类,导致系统复杂度变大,所以采用迪米特法则时要反复权衡,既要做到结构清晰,又要高内聚低耦合。
(6)合成复用原则 (Composite Reuse Principle):
尽量使用组合/聚合的方式,而不是使用继承。
二、Java的23种设计模式:
接下来我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析
1、创建型-工厂方法模式:
工厂方法模式分为三种:
(1)简单工厂模式:
建立一个工厂类,并定义一个接口对实现了同一接口的产品类进行创建。首先看下关系图:
(2)工厂方法模式:
工厂方法模式是对简单工厂模式的改进,简单工厂的缺陷在于不符合“开闭原则”,每次添加新产品类就需要修改工厂类,不利于系统的扩展维护。而工厂方法将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。UML关系图如下:
(3)静态工厂方法模式:
静态工厂模式是将工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
工厂方法模式详情文章:Java设计模式之创建型:工厂模式详解(简单工厂+工厂方法+抽象工厂)
2、创建型-抽象工厂模式:
抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
UML结构图如下:
抽象工厂模式详情:Java设计模式之创建型:工厂模式详解(简单工厂+工厂方法+抽象工厂)
3、创建型-建造者模式:
建造者模式将复杂产品的创建步骤分解在在不同的方法中,使得创建过程更加清晰,从而更精确控制复杂对象的产生过程;通过隔离复杂对象的构建与使用,也就是将产品的创建与产品本身分离开来,使得同样的构建过程可以创建不同的对象;并且每个具体建造者都相互独立,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。UML结构图如下:
建造者模式详情:Java设计模式之创建型:建造者模式
4、创建型-单例模式:
单例模式可以确保系统中某个类只有一个实例,该类自行实例化并向整个系统提供这个实例的公共访问点,除了该公共访问点,不能通过其他途径访问该实例。单例模式的优点在于:
- 系统中只存在一个共用的实例对象,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能
- 可以严格控制客户怎么样以及何时访问单例对象。
单例模式的写法有好几种,主要有三种:懒汉式单例、饿汉式单例、登记式单例。
单例模式详情:Java设计模式之创建型:单例模式
5、创建型-原型模式:
原型模式也是用于对象的创建,通过将一个对象作为原型,对其进行复制克隆,产生一个与源对象类似的新对象。UML类图如下:
在 Java 中,原型模式的核心是就是原型类 Prototype,Prototype 类需要具备以下两个条件:
- 实现 Cloneable 接口:
- 重写 Object 类中的 clone() 方法,用于返回对象的拷贝;
Object 类中的 clone() 方法默认是浅拷贝,如果想要深拷贝对象,则需要在 clone() 方法中自定义自己的复制逻辑。
- 浅复制:将一个对象复制后,基本数据类型的变量会重新创建,而引用类型指向的还是原对象所指向的内存地址。
- 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
使用原型模式进行创建对象不仅简化对象的创建步骤,还比 new 方式创建对象的性能要好的多,因为 Object 类的 clone() 方法是一个本地方法,直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显;
原型模式详情:Java设计模式之创建型:原型模式
上面我们介绍了5种创建型模式,下面我们就开始介绍下7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源,如下图:
6、结构型-适配器模式:
适配器模式主要用于将一个类或者接口转化成客户端希望的格式,使得原本不兼容的类可以在一起工作,将目标类和适配者类解耦;同时也符合“开闭原则”,可以在不修改原代码的基础上增加新的适配器类;将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性,但是缺点在于更换适配器的实现过程比较复杂。
所以,适配器模式比较适合以下场景:
- (1)系统需要使用现有的类,而这些类的接口不符合系统的接口。
- (2)使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
下面有个非常形象的例子很好地说明了什么是适配器模式:
适配器模式的主要实现有三种:类的适配器模式、对象的适配器模式、接口的适配器模式。三者的使用场景如下:
- 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
适配器模式详情:Java设计模式之结构型:适配器模式
7、结构型-装饰器模式:
装饰器模式可以动态给对象添加一些额外的职责从而实现功能的拓展,在运行时选择不同的装饰器,从而实现不同的行为;比使用继承更加灵活,通过对不同的装饰类进行排列组合,创造出很多不同行为,得到功能更为强大的对象;符合“开闭原则”,被装饰类与装饰类独立变化,用户可以根据需要增加新的装饰类和被装饰类,在使用时再对其进行组合,原有代码无须改变。装饰器模式的UML结构图如下:
但是装饰器模式也存在缺点,首先会产生很多的小对象,增加了系统的复杂性,第二是排错比较困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
装饰器模式详情:Java设计模式之结构型:装饰器模式
8、结构型-代理模式:
代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式中,代理对象主要起到一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。但缺点是在调用者与被调用者之间增加了代理对象,可能会造成请求的处理速度变慢。UML结构图如下:
代理模式详情:Java设计模式之结构型:代理模式
9、结构型-桥接模式:
桥接模式将系统的抽象部分与实现部分分离解耦,使他们可以独立的变化。为了达到让抽象部分和实现部分独立变化的目的,桥接模式使用组合关系来代替继承关系,抽象部分拥有实现部分的接口对象,从而能够通过这个接口对象来调用具体实现部分的功能。也就是说,桥接模式中的桥接是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来。
桥接模式符合“开闭原则”,提高了系统的可拓展性,在两个变化维度中任意扩展一个维度,都不需要修改原来的系统;并且实现细节对客户不透明,可以隐藏实现细节。但是由于聚合关系建立在抽象层,要求开发者针对抽象进行编程,这增加系统的理解和设计难度。桥接模式的UML结构图如下:
就像在Java中我们使用 JDBC 连接数据库时,在各个数据库之间进行切换,基本不需要动太多的代码,原因就是使用了桥接模式,JDBC 提供统一接口,每个数据库提供各自的实现,然后由桥接类创建一个连接数据库的驱动,使用某一个数据库的时候只需要切换一下就行。JDBC 的结构图如下:
在 JDBC 中,桥接模式的实现化角色 (Implementor) 为的 Driver 接口,具体实现化 (Concrete Implementor) 角色对应 MysqlDriver、OracleDriver 和 MariadbDriver,扩展抽象化 (Refined Abstraction) 角色对应 DriverManager,不具有抽象化 (Abstraction) 角色作为扩展抽象化角色的父类。
桥接模式详情:Java设计模式之结构型:桥接模式
10、结构型-外观模式:
外观模式通过对客户端提供一个统一的接口,用于访问子系统中的一群接口。使用外观模式有以下几点好处:
(1)更加易用:使得子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观类交互就可以了;
(2)松散耦合:将客户端与子系统解耦,让子系统内部的模块能更容易扩展和维护。
(3)更好的划分访问层次:通过合理使用 Facade,可以更好地划分访问的层次,有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到门面中,这样既方便客户端使用,也很好地隐藏了内部的细节。
但是如果外观模式对子系统类做太多的限制则减少了可变性和灵活性,所以外观模式适用于为复杂子系统提供一个简单接口,提高系统的易用性场景 以及 引入外观模式将子系统与客户端进行解耦,提高子系统的独立性和可移植性。
外观模式的UML结构图如下:
外观模式详情: Java设计模式之结构型:外观模式
11、结构型-组合模式:
组合模式将叶子对象和容器对象进行递归组合,形成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性,能够像处理叶子对象一样来处理组合对象,无需进行区分,从而使用户程序能够与复杂元素的内部结构进行解耦。
组合模式最关键的地方是叶子对象和组合对象实现了相同的抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建类进行编程,这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。组合模式的UML结构图如下:
组合模式详情: Java设计模式之结构型:组合模式
12、结构型-享元模式:
享元模式通过共享技术有效地支持细粒度、状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量,从而节省资源。
享元模式的核心是享元工厂类,享元工厂类维护了一个对象存储池,当客户端需要对象时,首先从享元池中获取,如果享元池中存在对象实例则直接返回,如果享元池中不存在,则创建一个新的享元对象实例返回给用户,并在享元池中保存该新增对象,这点有些单例的意思。
工厂类通常会使用集合类型来保存对象,如 HashMap、Hashtable、Vector 等等,在 Java 中,数据库连接池、线程池等都是用享元模式的应用。
享元模式的UML结构图如下:
Java 中,String 类型就是使用享元模式,String 对象是 final 类型,对象一旦创建就不可改变。而 Java 的字符串常量都是存在字符串常量池中的,JVM 会确保一个字符串常量在常量池中只有一个拷贝。
而且提到共享池,我们也很容易联想到 Java 里面的JDBC连接池,通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!
享元模式详情:Java设计模式之结构型:享元模式
前面我们介绍了7种结构型设计模式,接下来我们介绍一下11种行为型设计模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。先来张图,看看这11中模式的关系:
13、行为型-策略模式:
将类中经常改变或者可能改变的部分提取为作为一个抽象策略接口类,然后在类中包含这个对象的实例,这样类实例在运行时就可以随意调用实现了这个接口的类的行为。
比如定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化,这就是策略模式。UML结构图如下:
策略模式的优点在于可以动态改变对象的行为;但缺点是会产生很多策略类,并且策略模式的决定权在用户,系统只是提供不同算法的实现,所以客户端必须知道所有的策略类,并自行决定使用哪一个策略类;
策略模式适用用于以下几种场景:
- (1)应用程序需要实现特定的功能服务,而该程序有多种实现方式使用,所以需要动态地在几种算法中选择一种
- (2)一个类定义了多种行为算法,并且这些行为在类的操作中以多个条件语句的形式出现,就可以将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
策略模式详情:Java设计模式之行为型:策略模式
14、行为型-模板方法:
模板方法是基于继承实现的,在抽象父类中声明一个模板方法,并在模板方法中定义算法的执行步骤(即算法骨架)。在模板方法模式中,可以将子类共性的部分放在父类中实现,而特性的部分延迟到子类中实现,只需将特性部分在父类中声明成抽象方法即可,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,不同的子类可以以不同的方式来实现这些逻辑。
模板方法模式的优点在于符合“开闭原则”,也能够实现代码复用,将不变的行为转移到父类,去除子类中的重复代码。但是缺点是不同的实现都需要定义一个子类,导致类的个数的增加使得系统更加庞大,设计更加抽象。模板方法模式的UML图如下:
模板方法详情:Java设计模式之行为型:模板方法模式
15、行为型-责任链模式:
职责链可以将请求的处理者组织成一条链,并将请求沿着链传递,如果某个处理者能够处理请求则处理,否则将该请求交由上级处理。客户端只需将请求发送到职责链上,无须关注请求的处理细节,通过职责链将请求的发送者和处理者解耦了,这也是职责链的设计动机。
职责链模式可以简化对象间的相互连接,因为客户端和处理者都没有对方明确的信息,同时处理者也不知道职责链中的结构,处理者只需保存一个指向后续者的引用,而不需要保存所有候选者的引用。
另外职责链模式增加了系统的灵活性,我们可以任意增加或更改处理者,甚至更改处理者的顺序,不过有可能会导致一个请求无论如何也得不到处理,因为它可能被放置在链末端。
所以责任链模式有以下几个优点:
- (1)降低耦合度,将请求的发送者和接收者解耦。反映在代码上就是不需要在类中写很多丑陋的 if….else 语句,如果用了职责链,相当于我们面对一个黑箱,只需将请求递交给其中一个处理者,然后让黑箱内部去负责传递就可以了。
- (2)简化了对象,使得对象不需要链的结构。
- (3)增加系统的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或者删除处理者
- (4)增加新的请求处理类很方便。
但是责任链模式也存在一些缺点:
- (1)不能保证请求一定被成功处理
- (2)系统性能将受到一定影响,并且可能会造成循环调用。
- (3)可能不容易观察运行时的特征,而且在进行代码调试时不太方便,有碍于除错。
责任链模式的UML结构图如下:
责任链模式详情:Java设计模式之行为型:责任链模式
16、行为型-观察者模式:
观察者模式又称为 发布-订阅模式,定义了对象之间一对多依赖关系,当目标对象(被观察者)的状态发生改变时,它的所有依赖者(观察者)都会收到通知。一个观察目标可以对应多个观察者,而这些观察者之间没有相互联系,所以能够根据需要增加和删除观察者,使得系统更易于扩展,符合开闭原则;并且观察者模式让目标对象和观察者松耦合,虽然彼此不清楚对方的细节,但依然可以交互,目标对象只知道一个具体的观察者列表,但并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。
但观察者模式的缺点在于如果存在很多个被观察者的话,那么将需要花费一定时间通知所有的观察者,如果观察者与被观察者之间存在循环依赖的话,那么可能导致系统崩溃,并且观察者模式没有相应的机制让观察者知道被观察对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。观察者模式的UML结构图如下:
观察者模式详情:Java设计模式之行为型:观察者模式
17、行为型-访问者模式:
访问者模式就是一种分离对象数据结构与行为 (基于数据结构的操作) 的方法,通过这种分离,达到为一个被访问者动态添加新的操作而无需做其它修改的效果,使得添加作用于这些数据结构的新操作变得简单,并且不需要改变各数据结构,为不同类型的数据结构提供多种访问操作方式,这样是访问者模式的设计动机。
除了使新增访问操作变得更加简单,也能够在不修改现有类的层次结构下,定义该类层次结构的操作,并将有关元素对象的访问行为集中到一个访问者对象中,而不是分散搞一个个的元素类中。
但访问者模式的缺点在于让增加新的元素类变得困难,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求;
所以访问者模式适用于对象结构中很少改变,但经常需要在此对象结构上定义新的操作的系统,使得算法操作的增加变得简单;或者需要对一个对象结构中进行很多不同并且不相关的操作,并且需要避免让这些操作污染这些对象,也不希望在增加新操作时修改这些类的场景。
访问者模式的UML结构图如下:
从上面的 UML 结构图中我们可以看出,访问者模式主要分为两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,主要用于声明一些操作;一个是元素层次结构,提供了抽象元素和具体元素,主要用于声明 accept 操作;而对象结构 ObjectStructure 作为两者的桥梁,存储了不同类型的对象,以便不同的访问者来访问,相同访问者可以以不同的方式访问不同的元素,所以在访问者模式中增加新的访问者无需修改现有代码,可扩展行强。
在访问者模式使用了双分派技术,所谓双分派技术就是在选择方法的时候,不仅仅要根据消息接收者的运行时区别,还要根据参数的运行时区别。在访问者模式中,客户端将具体状态当做参数传递给具体访问者,这里完成第一次分派,然后具体访问者作为参数的“具体状态”中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派。双分派意味着得到的执行操作决定于请求的种类和接受者的类型。
访问者模式详情:Java设计模式之行为型:访问者模式
18、行为型-中介者模式:
中介者模式通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,对象间一对多的关联转变为一对一的关联,简化对象间的关系,便于理解;各个对象之间的关系被解耦,每个对象不再和它关联的对象直接发生相互作用,而是通过中介者对象来与关联的对象进行通讯,使得对象可以相对独立地使用,提高了对象的可复用和系统的可扩展性。
在中介者模式中,中介者类处于核心地位,它封装了系统中所有对象类之间的关系,除了简化对象间的关系,还可以对对象间的交互进行进一步的控制。中介者模式的UML结构图如下:
但是,中介者对象封装了对象之间的关联关系,导致中介者对象变得比较庞大复杂,所承担的责任也比较多,维护起来也比较困难,它需要知道每个对象和他们之间的交互细节,如果它出问题,将会导致整个系统都会出问题。
中介者模式详情:Java设计模式之行为型:中介者模式
19、行为型-命令模式:
命令模式的本质是将请求封装成对象,将发出命令与执行命令的责任分开,命令的发送者和接收者完全解耦,发送者只需知道如何发送命令,不需要关心命令是如何实现的,甚至是否执行成功都不需要理会。命令模式的关键在于引入了抽象命令接口,发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
使用命令模式的优势在于降低了系统的耦合度,而且新命令可以很方便添加到系统中,也容易设计一个组合命令。但缺点在于会导致某些系统有过多的具体命令类,因为针对每一个命令都需要设计一个具体命令类。
命令模式的UML结构图如下:
命令模式详情: Java设计模式之行为型:命令模式
20、行为型-状态模式:
状态模式,就是允许对象在内部状态发生改变时改变它的行为,对象看起来就好像修改了它的类,也就是说以状态为原子来改变它的行为,而不是通过行为来改变状态。
当对象的行为取决于它的属性时,我们称这些属性为状态,那该对象就称为状态对象。对于状态对象而言,它的行为依赖于它的状态,比如要预订房间,只有当该房间空闲时才能预订,想入住该房间也只有当你预订了该房间或者该房间为空闲时。对于这样的一个对象,当它的外部事件产生互动的时候,其内部状态就会发生变化,从而使得他的行为也随之发生变化。
状态模式的UML结构图如下:
从上面的UML结构图我们可以看出状态模式的优点在于:
(1)封装了转换规则,允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
(2)将所有与状态有关的行为放到一个类中,可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
但是状态模式的缺点在于:
(1)需要在枚举状态之前需要确定状态种类
(2)会导致增加系统类和对象的个数。
(3)对 “开闭原则” 的支持并不友好,新增状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
所以状态模式适用于:代码中包含大量与对象状态有关的条件语句,以及对象的行为依赖于它的状态,并且可以根据它的状态改变而改变它的相关行为。
状态模式详情:Java设计模式之行为型:状态模式
21、行为型-备忘录模式:
备忘录模式提供了一种恢复状态的机制,在不破坏封装的前提下,捕获对象的某个时刻内部状态,并保存在该对象之外,保证该对象能够恢复到某个历史状态;备忘录模式将保存的细节封装在备忘录中,除了创建它的创建者之外其他对象都不能访问它,并且实现了即使要改变保存的细节也不影响客户端。但是备忘录模式都是多状态和多备份的,会早用较多的内存,消耗资源。备忘录模式的额UML结构图如下:
备忘录模式的核心就是备忘录 Memento,在备忘录中存储的就是原发器 Originator 的部分或者所有的状态信息,而这些状态信息是不能够被其他对象所访问的,也就是说我们是不能使用备忘录之外的对象来存储这些状态信息,如果暴漏了内部状态信息就违反了封装的原则,故备忘录除了原发器外其他对象都不可以访问。所以为了实现备忘录模式的封装,我们需要对备忘录的访问做些控制:
(1)对原发器:可以访问备忘录里的所有信息。
(2)对负责人 caretaker:不可以访问备忘录里面的数据,但是他可以保存备忘录并且可以将备忘录传递给其他对象。
(3)其他对象:不可访问也不可以保存,它只负责接收从负责人那里传递过来的备忘录同时恢复原发器的状态。
备忘录模式详情:Java设计模式之行为型:备忘录模式
22、行为型-迭代器模式:
迭代器模式提供一种访问集合中的各个元素,而不暴露其内部表示的方法。将在元素之间游走的职责交给迭代器,而不是集合对象,从而简化集合容器的实现,让集合容器专注于在它所应该专注的事情上,更加符合单一职责原则,避免在集合容器的抽象接口层中充斥着各种不同的遍历操作。迭代器模式的UML结构图如下:
迭代器模式详情:Java设计模式之行为型:迭代器模式
23、行为型-解释器模式:
解释器模式,就是定义语言的文法,并建立一个解释器来解释该语言中的句子,通过构建解释器,解决某一频繁发生的特定类型问题实例。
解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中,它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
解释器模式中除了能够使用文法规则来定义一个语言,还能通过使用抽象语法树来更加直观表示、更好地地表示一个语言的构成,每一颗抽象语法树对应一个语言实例。抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符和非终结符类。 在解释器模式中由于每一种终结符表达式、非终结符表达式都会有一个具体的实例与之相对应,所以系统的扩展性比较好。
解释器模式的UML如下:
解释器模式详情:Java设计模式之行为型:解释器模式
相关推荐阅读:
参考文章:
-
Java设计模式 --- 七大常用设计模式示例归纳
2018-10-07 16:35:24设计模式分为三种类型,共23种: 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 行为型... -
C++常用的11种设计模式
2021-03-08 01:42:15这里写目录标题工厂模式单例模式(懒汉式、饿汉式)适配器模式外观模式代理模式包装模式(装饰器模式)桥接模式模板方法模式策略模式观察者模式责任链模式 工厂模式 定义:将工厂变成一个抽象类,在里面定义一个纯虚... -
快速梳理23种常用的设计模式
2018-11-17 22:54:34本文旨在快速梳理常用的设计模式,了解每个模式主要针对的是哪些情况以及其基础特征,每个模式前都有列举出一个或多个可以深入阅读的参考网页,以供读者详细了解其实现。 快速回忆 一、创建型 单例(Singleton... -
前端开发中常用的几种设计模式
2021-08-17 15:05:54设计模式是对软件设计开发过程中反复出现的某类...设计模式可以分为三大类: 结构型模式(Structural Patterns):通过识别系统中组件间的简单关系来简化系统的设计。 创建型模式(Creational Patterns):处理对象的创.. -
Java中常用的设计模式
2019-03-15 17:25:14使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块... -
23种设计模式常用模式
2018-08-08 09:14:41设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段... -
Android中常用的设计模式
2018-05-04 14:41:04Android开发中常见的设计模式Android中常见的设计模式Android设计模式之23种设计模式一览Android中常用设计模式二:然后总结一下其总结内容多根据参考文献文档而来,还请了解文档内容(一)什么是设计模式1.... -
Java常用的设计模式
2019-03-10 17:51:04设计模式是编程解决实际问题或类似问题的最佳实践,Java编程中处处都是对象,...设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:... -
23种常用设计模式(C++)
2018-05-02 11:22:05 Part One: Methods for constrcting a new object: ...尽管可以通过类的构造函数生成对象,但是,如果派生类的数量很大——即使几十个不同的派生类——对于程序设计而言也是困难的。这里有两个困难:其一是可读性 -
Android中常用设计模式
2018-04-07 10:27:25总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元... -
框架(Framework)中常用设计模式分析
2021-11-14 10:04:33抽象工厂(Abstract Factory)建造模式(Builder)单例模式(Singleton)原型模式(Prototype)结构型模式设计及分析适配器模式 (Adapter)装饰模式 (Decorator)代理模式 (Proxy)享元模式(Flyweight)门面模式(Façade... -
Java 中几种常用设计模式
2018-08-09 16:50:32总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元... -
Spring中常用设计模式总结
2018-03-30 22:49:41Spring中常用的设计模式总结之前写的几篇设计模式总结,其实主要是为了阅读spring源码服务的,这里对之前的一些设计模式来一个简单的总结1、单例模式总结的博文如下:单例模式总结单例模式,主要目的是保证实例的... -
详解 Javascript十大常用设计模式
2018-06-21 17:39:11转载自:https://juejin.im/entry/58c280b1da2f600d8725b887一...这时候需要使用工厂模式。 简单的工厂模式可以理解为解决多个相似的问题;这也是她的优点;比如如下代码: function CreatePerson(name,age,sex) { ... -
J2EE下的常用设计模式
2018-03-10 13:30:58其次恰当的使用设计模式可以用以解决特定场景的问题的一系列方法,帮助我们改善系统的设计,增强系统的健壮性、可扩展性,为以后铺平道路。 最后用网络上的一句概括来说,设计模式(Designpattern)就是一套被... -
常用设计模式及其 Java 实现
2018-08-05 14:06:05设计模式是在不断出现的特定情境下,针对特定问题,可以重复使用的特定解决模式(套路)。本文按照创建型、结构型、行为型三大类,总结了常见的 24 种设计模式的使用要点,包括适用场景、解决方案、及其相应的 Java ... -
Android 常用设计模式(一)
2015-07-27 08:37:01由于项目变更的频繁性,作为一名程序员,我们需要掌握设计模式的必要性,就不言而喻~~,下面就是一些我自己学习的设计模式总结。 接下来,主要是针对几个比较常用模式进行讲解,主要是以下几种: 观察者模式 ... -
常用设计模式-抽象工厂
2020-11-19 17:18:48工厂模式按照《Java与模式》中的提法分为三类: 1. 简单工厂模式(Simple Factory) 2. 工厂方法模式(Factory Method) 3. 抽象工厂模式(Abstract Factory) 工厂方法模式,提供一个创建一系列相关或相互依赖对象的... -
23 种设计模式详解(全23种)
2019-06-09 00:21:59总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元... -
安卓常用6种设计模式总结
2018-08-19 23:10:19转载自... ... 由于项目变更的频繁性,作为一名程序员,我们需要掌握设计模式的必要性,就不言而喻~~,下面就是一些我自己学习的设计模式总结。 接下来,主要... -
java开发中的常用的设计模式
2018-05-28 13:03:34设计模式(Design Patterns) ——可复用面向对象软件的基础设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人... -
常见设计模式—单例模式(Singleton)
2020-03-15 20:14:59对于常用的23种设计模式,这里笔者会根据自己学习和出现频率、重要程度进行学习记录吧。 单例模式 单例模式(Singleton Pattern)是设计模式中最简单的模式之一,属于创建型模式。这种设计模式主要是类的对象只有一... -
23个设计模式中最常用的设计模式
2018-04-04 14:52:40行为型模式:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。 代理模式: 静态代理 ... -
什么是设计模式?程序员如何学好设计模式?
2021-11-30 00:15:58前几天,我给大家介绍了算法和数据结构的基础知识。后来又有小伙伴私信问我:“小灰,你能不能也讲一讲设计模式的相关知识?”没问题!对于程序员来说,设计模式也是必须要掌握的一项核心知识,我今天就...