设计模式
一、设计模式的六大设计原则
软件设计开发原则
为了让代码有更好的可重用性,可读性,可靠性,可维护性,诞生出了很多软件设计的原则,这6大设计原则是我们要掌握的,将六大原则的英文⾸字母拼在一起就是SOLID(稳定的),所以也称之为SOLID原则。
1、单一职责原则
一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
是实现高内聚、低耦合的指导方针。
解释:
高内聚:尽可能类的每个成员方法只完成一件事(最大限度的聚合)。
模块内部的代码, 相互之间的联系越强,内聚就越高, 模块的独立性就越好。
低耦合: 减少类内部一个成员方法调用另一个成员方法, 不要牵一发动全身。
2、开闭原则
对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个可插拔的效果。
3、里氏替换原则
任何基类可以出现的地方,子类一定可以出现。
在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
4、依赖倒转原则
是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体。
高层模块不应该依赖低层模块,二者都应该依赖其抽象。
5、接口隔离原则
客户端不应该依赖那些它不需要的接口。
使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度。
6、迪米特法则
最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及。
通过引入一个合理的第三者来降低现有对象之间的耦合度。
二、常见的三大设计模式分类
1、创建型模式
提供了一种在创建对象的同时隐藏创建逻辑的方式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
常用:单例模式、工厂方法模式、抽象工厂模式、建造者模式。
不常用:原型模式。
2、结构型模式
关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
常用:适配器模式、装饰器模式、桥接模式、代理模式。
不常用:组合模式、外观模式、享元模式。
3、行为型模式
特别关注对象之间的通信。
常用:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、模板模式。
不常用:备忘录模式、命令模式。
几乎不用:访问者模式、中介者模式、解释器模式。
三、设计模式详解
1、单例模式
单例意思只包含一个对象被称为单例的特殊类,通过单例模式可以保证系统中,应用该模式的类只有一个对象实例。
两种不同的实现方式:
懒汉:就是所谓的懒加载,延迟创建对象。
饿汉:与懒汉相反,提前创建对象。
实现步骤:
私有化构造函数。
提供获取单例的方法。
懒汉式:
DCL 双重检查锁定 (Double-Checked-Locking),在多线程情况下保持高性能
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
// 第一次检测
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
// 多线程环境下可能会出现问题的地方
instance = new Singleton();
}
}
return instance;
}
}
饿汉式:
饿汉方式:提前创建好对象。
优点:实现简单,没有多线程同步问题。
缺点:不管有没使用,instance对象一直占着这段内存。
如何选择:
如果对象不大,且创建不复杂,直接用饿汉的方式即可,其他情况则采用懒汉实现方式。
2、工厂模式
它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式有 3 种不同的实现方式:
简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单 一,可扩展性相对较差。
工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强。
抽象工厂模式:基于上述两种模式的拓展,且支持细化产品。
应用场景:
解耦:分离职责,把复杂对象的创建和使用的过程分开。
复用代码降低维护成本:
如果对象创建复杂且多处需用到,如果每处都进行编写,则很多重复代码,如果业务逻辑发生了改变,需要四处修改。
使用工厂模式统一创建,则只要修改工厂类即可,降低成本。
简单工厂模式:
又称静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类或接口。
工厂方法是静态方法,可通过类名直接调用,只需要传入简单的参数即可。
核心组成:
IProduct:抽象产品类,描述所有实例所共有的公共接口。
Product:具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个。
Factory:工厂类,简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。
实现步骤:
创建抽象产品类,里面有产品的抽象方法,由具体的产品类去实现。
创建具体产品类,继承父类,并实现具体方法。
创建工厂类,提供了一个静态方法createXXX用来生产产品,只需要传入想要生产的产品名称。
优点:将对象的创建和对象本身业务处理分离,可以降低系统的耦合度,使得两者修改起来都相对容易。
缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则相违背。
会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不需要用工厂模式。
代码实现:
public interface Pay {
/**
* 支付接口
*/
void pay();
}
public class AliPay implements Pay{
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
public class WechatPay implements Pay{
@Override
public void pay() {
System.out.println("微信支付");
}
}
public class PayFactory {
/**
* 根据参数 返回对应的支付对象
* @param payType
* @return
*/
public static Pay createPay(String payType) {
if (payType == null) {
return null;
} else if (payType.equalsIgnoreCase("ALI_PAY")) {
return new AliPay();
} else if (payType.equalsIgnoreCase("WECHAT_PAY")) {
return new WechatPay();
}
//想拓展,直接编写更多
return null;
}
}
public class TestPay {
public static void main(String[] args) {
Pay aliPay = PayFactory.createPay("ALI_PAY");
aliPay.pay();
Pay wechatPay = PayFactory.createPay("WECHAT_PAY");
wechatPay.pay();
}
}
工厂方法模式:
又称工厂模式,是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
通过工厂父类定义负责创建产品的公共接口,通过子类来确定所需要创建的类型。
相比简单工厂而言,此种方法具有更多的可扩展性和复用性,同时也增强了代码的可读性。
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类。
核心组成:
IProduct:抽象产品类,描述所有实例所共有的公共接口。
Product:具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个。
IFactory:抽象工厂类,描述具体工厂的公共接口。
Factory:具体工场类,实现创建产品类对象,实现抽象工厂类的接口,如果有多个需要定义多个。
代码实现:
public interface Pay {
/**
* 支付接口
*/
void pay();
}
public class AliPay implements Pay{
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
public class WechatPay implements Pay{
@Override
public void pay() {
System.out.println("微信支付");
}
}
public interface PayFactory {
Pay getPay();
}
public class AliPayFactory implements PayFactory{
@Override
public Pay getPay() {
return new AliPay();
}
}
public class WechatPayFactory implements PayFactory{
@Override
public Pay getPay() {
return new WechatPay();
}
}
public class Test {
public static void main(String[] args) {
AliPayFactory aliPayFactory = new AliPayFactory();
Pay aliPayFactoryPay = aliPayFactory.getPay();
aliPayFactoryPay.pay();
WechatPayFactory wechatPayFactory = new WechatPayFactory();
Pay wechatPayFactoryPay = wechatPayFactory.getPay();
wechatPayFactoryPay.pay();
}
}
优点:
符合开闭原则,增加一个产品类,只需要实现其具体的产品类和具体的工厂类。
符合单一职责原则,每个工厂只负责生产对应的产品。
使用者只需要知道产品的抽象类,无须关心其实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
每增加一个产品都需要实现其具体产品类和具体工厂类。
抽象工厂模式:
基于上述两种模式的拓展,是工厂方法模式的升级版,当需要创建的产品有多个产品线时使用抽象工⼚厂模式是比较好的选择,当抽象工厂模式中每一个具体工厂类只创建一个产品对象时退化成工厂方法模式。
背景:
工厂方法模式引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题。
但工厂方法模式中每个工厂只创建一类具体类的对象,后续发展可能会导致工厂类过多,因此将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产,强调的是一系列相关的产品对象。
实现步骤:
1、定义两个接口 Pay、Refund。
2、创建具体的Pay产品、Refund产品。
3、创建抽象工厂 OrderFactory 接口,里面两个方法 createPay和createRefund。
4、创建支付宝产品族AliOderFactory,实现OrderFactory抽象工厂。
5、创建微信产品族WechatOderFactory,实现OrderFactory抽象工厂。
6、定义一个超级工厂创造器,通过传递参数获取对应的工厂。
代码实现:
public interface Pay {
/**
* 支付接口
*/
void pay();
}
public interface Refund {
/**
* 退款接口
*/
void refund();
}
public class AliPay implements Pay{
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
public class AliRefund implements Refund{
@Override
public void refund() {
System.out.println("支付宝退款");
}
}
public class WechatPay implements Pay{
@Override
public void pay() {
System.out.println("微信支付");
}
}
public class WechatRefund implements Refund{
@Override
public void refund() {
System.out.println("微信退款");
}
}
public interface OrderFactory {
Pay createPay();
Refund createRefund();
}
public class AliOrderFactory implements OrderFactory{
@Override
public Pay createPay() {
return new AliPay();
}
@Override
public Refund createRefund() {
return new AliRefund();
}
}
public class WechatOrderFactory implements OrderFactory{
@Override
public Pay createPay() {
return new WechatPay();
}
@Override
public Refund createRefund() {
return new WechatRefund();
}
}
public class FactoryProducer {
public static OrderFactory getFactory(String type){
if(type.equalsIgnoreCase("ALI")){
return new AliOrderFactory();
}else if (type.equalsIgnoreCase("WECHAT")){
return new WechatOrderFactory();
}
return null;
}
}
public class Test {
public static void main(String[] args) {
OrderFactory ali = FactoryProducer.getFactory("ALI");
ali.createPay().pay();
ali.createRefund().refund();
OrderFactory wechat = FactoryProducer.getFactory("WECHAT");
wechat.createPay().pay();
wechat.createRefund().refund();
}
}
3、原型模式
是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能。
工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。
应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式。
核心组成:
Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口。
ConcretePrototype : 具体原型类。
Client: 让一个原型对象克隆自身从而创建一个新的对象。
应用场景:
创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得。
如果系统要保存对象的状态,做备份使用。
问题:
通过对一个类进行实例化来构造新对象,不同的是,原型模式是通过拷贝一个现有对象生成新对象的。
浅拷贝实现 Cloneable,深拷贝是通过实现Serializable 读取二进制流。
拓展:
浅拷贝:
如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象。
如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
通过覆盖Object类的clone()方法可以实现浅克隆。
深拷贝:
无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等方式来实现。
原型模式是内存二进制流的拷贝,比new对象性能高很多,使用的时候记得注意是选择浅拷贝还是深拷贝。
优点:
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率。
可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态。
缺点:
需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引用时,需要对每一层对象对应的类都必须支持深克隆。
浅克隆代码实现:
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private List<String> list = new ArrayList<>();
public Person(){
System.out.println("调用构造函数");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
/**
* 深拷贝
* @return
*/
public Object deepClone() {
try {
//输出 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//输入 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Person copyObj = (Person) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("大王");
person1.setAge(18);
person1.getList().add("a");
// 浅拷贝
// Person person2 = person1.clone();
// 深拷贝
Person person2 = (Person) person1.deepClone();
person2.setName("小王");
person2.getList().add("b");
System.out.println("person1 = " + person1.getName() + ", age = " + person1.getAge() + ", list = " + person1.getList());
System.out.println("person2 = " + person2.getName() + ", age = " + person2.getAge() + ", list = " + person2.getList());
}
}
4、建造者模式
使用多个简单的对象一步一步构建成一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
允许用户只通过指定复杂对象的类型和内容就可以构建它们,不需要知道内部的具体构建细节。
场景举例:
电脑有低配、高配,组装需要CPU、内存、电源、硬盘、主板等。
核心组成:
Product:产品角色。
Builder:抽象建造者,定义多个通用方法和构建方法。
ConcreteBuilder:具体建造者,可以有多个。
Director:指挥者,控制整个组合过程,将需求交给建造者,由建造者去创建对象。
代码实现:
public class Computer {
private String cpu;
private String memory;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
'}';
}
}
/**
* 声明建造者的公共方法
*/
public interface Builder {
void buildCpu();
void buildMemory();
Computer createComputer();
}
/**
* 具体的建造者,实现Builder来创建不同的产品
*/
public class LowComputerBuilder implements Builder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("低配cpu");
}
@Override
public void buildMemory() {
computer.setMemory("低配内存");
}
@Override
public Computer createComputer() {
return computer;
}
}
public class HighComputerBuilder implements Builder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("高配cpu");
}
@Override
public void buildMemory() {
computer.setMemory("高配内存");
}
@Override
public Computer createComputer() {
return computer;
}
}
/**
* 将产品和创建过程解耦,使用相同的创建过程创建不同的产品,控制产品生产过程
* Director是全程知道组装过程,具体的细节还是builder去做
*/
public class Director {
public Computer create(Builder builder){
builder.buildCpu();
builder.buildMemory();
return builder.createComputer();
}
}
public class Test {
public static void main(String[] args) {
Director director = new Director();
Computer lowComputer = director.create(new LowComputerBuilder());
Computer highComputer = director.create(new HighComputerBuilder());
System.out.println(lowComputer.toString());
System.out.println(highComputer.toString());
}
}
优点:
客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦。
每一个具体建造者都相对独立,而与其他的具体建造者无关,更加精细地控制产品的创建过程。
增加新的具体建造者无须修改原有类库的代码,符合开闭原则。
建造者模式结合链式编程来使用,代码上更加美观。
缺点:
建造者模式所创建的产品一般具有较多的共同点,如果产品差异大则不建议使用。
建造者模式与抽象工厂模式的比较:
建造者模式返回一个组装好的完整产品 , 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
5、适配器模式
见名知意,是作为两个不兼容的接口之间的桥梁,属于结构型模式。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
常见的几类适配器:
类适配器模式:
想将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象适配器模式:
想将一个对象转换成满足另一个新接口的对象时,可以创建一个适配器类,持有原类的一个实例,在适配器类的方法中,调用实例的方法就行。
接口适配器模式:
不想实现一个接口中所有的方法时,可以创建一个Adapter,实现所有方法,在写别的类的时候,继承Adapter类即可。
接口适配器模式代码实现:
public interface IPay {
/**
* 支付接口
*/
void pay();
/**
* 退款接口
*/
void refund();
}
public class PayAdapter implements IPay {
@Override
public void pay() {
}
@Override
public void refund() {
}
}
public class Pay extends PayAdapter{
@Override
public void pay() {
System.out.println("支付");
}
}
public class Refund extends PayAdapter{
@Override
public void refund() {
System.out.println("退款");
}
}
6、桥接模式
public interface Color {
void color();
}
public class RedColor implements Color{
@Override
public void color() {
System.out.println("红色");
}
}
public class BlueColor implements Color{
@Override
public void color() {
System.out.println("蓝色");
}
}
public abstract class Phone {
/**
* 通过组合的方式来桥接其他行为
*/
protected Color color;
public void setColor(Color color) {
this.color = color;
}
/**
* 手机的运行方法
*/
abstract public void run();
}
public class HuaWeiPhone extends Phone{
public HuaWeiPhone(Color color){
super.setColor(color);
}
@Override
public void run() {
color.color();
System.out.println("华为手机");
}
}
public class ApplePhone extends Phone{
public ApplePhone(Color color){
super.setColor(color);
}
@Override
public void run() {
color.color();
System.out.println("苹果手机");
}
}
public class Test {
public static void main(String[] args) {
HuaWeiPhone huaWeiPhone = new HuaWeiPhone(new RedColor());
huaWeiPhone.run();
ApplePhone applePhone = new ApplePhone(new BlueColor());
applePhone.run();
}
}
优点:
抽象和实现的分离。
优秀的扩展能力,符合开闭原则。
缺点:
增加系统的理解与设计难度。
使用聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程,比如抽象类汽车,里面聚合了颜色类,有点像对象适配器。
桥接模式和适配器模式的对比:
用于设计的不同阶段。
桥接模式用于设计的前期,精细化的设计,让系统更加灵活。
适配器模式用于设计完成之后,发现类、接口之间无法一起工作,需要进行填坑。
7、组合模式
又叫部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构,可以更好的实现管理操作。
组合模式使得用户可以使用一致的方法操作单个对象和组合对象。
部分-整体对象的基本操作多数是一样的,但是应该还会有不一样的地方。
核心:组合模式可以使用一棵树来表示。
应用场景:
文件夹和文件,都有增加、删除等api,也有层级管理关系。
公司也是,总公司下有子公司,每个公司大部分的部门都类似。
当我们的要处理的对象可以生成一颗树形结构,我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子。
代码实现:
/**
* 根节点,抽象类,通用的属性和方法
*/
public abstract class Root {
private String name;
public Root(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void addFile(Root root);
public abstract void removeFile(Root root);
public abstract void display(int depth);
}
/**
* 具体的文件夹,里面可以添加子文件夹或者文件
*/
public class Folder extends Root{
List<Root> folders = new ArrayList<>();
public Folder(String name){
super(name);
}
public List<Root> getFolders() {
return folders;
}
public void setFolders(List<Root> folders) {
this.folders = folders;
}
@Override
public void addFile(Root root) {
folders.add(root);
}
@Override
public void removeFile(Root root) {
folders.remove(root);
}
@Override
public void display(int depth) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < depth; i++){
builder.append("-");
}
// 打印横线和当前文件名
System.out.println(builder.toString() + this.getName());
for (Root folder : folders) {
// 每个下级多2个横线
folder.display(depth + 2);
}
}
}
/**
* 这个类没有节点,不存储其他子类数组,所以是叶子节点
*/
public class File extends Root{
public File(String name){
super(name);
}
@Override
public void addFile(Root root) {
}
@Override
public void removeFile(Root root) {
}
@Override
public void display(int depth) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < depth; i++){
builder.append("-");
}
// 打印横线和当前文件名
System.out.println(builder.toString() + this.getName());
}
}
public class Test {
public static void main(String[] args) {
// 创造根文件夹
Root root = new Folder("C://");
// 建立子文件夹
Root desktop = new Folder("桌面");
Root myComputer = new Folder("我的电脑");
// 建立子文件
Root file = new File("Hello.java");
// 建立文件夹关系
root.addFile(desktop);
root.addFile(myComputer);
// 建立文件关系
myComputer.addFile(file);
// 从0级开始显示,每个下级多2条横线
root.display(0);
}
}
8、装饰器模式
也叫包装设计模式,属于结构型模式,它是作为现有的类的一个包装,允许向一个现有的对象添加新的功能,同时又不改变其结构。
给对象增加功能,一般两种方式 继承或关联组合,将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,比继承模式更加灵活。
应用场景:
汽车,为了显得突出,店家提供多种改装方案,加个大的喇叭、加个探照灯等,经过装饰之后成为目的更明确的汽车,更能解决问题。像这种不断为对象添加装饰的模式就叫 Decorator 模式,Decorator 指的是装饰物。
以动态、透明的方式给单个对象添加职责,但又能不改变其结构。
角色(装饰者和被装饰者有相同的超类(Component))
抽象组件(Component)
定义装饰方法的规范,最初的汽车,仅定义了汽车的API。
被装饰者(ConcreteComponent)
Component的具体实现,也就是我们要装饰的具体对象。
实现了核心角色的具体汽车。
装饰者组件(Decorator)
定义具体装饰者的行为规范, 和Component角色有相同的接口,持有组件(Component)对象的实例引用。
汽车组件都有名称和价格。
具体装饰物(ConcreteDecorator)
负责给构件对象装饰附加的功能,比如喇叭,探照灯。
代码实现:
/**
* 通用接口
*/
public interface Car {
String getName();
int getPrice();
}
/**
* 具体的被装饰者
*/
public class BigCar implements Car{
private String name = "大汽车";
private int price = 20000;
@Override
public String getName() {
return name;
}
@Override
public int getPrice() {
return price;
}
}
/**
* 具体的被装饰者
*/
public class SmallCar implements Car{
private String name = "小汽车";
private int price = 10000;
@Override
public String getName() {
return name;
}
@Override
public int getPrice() {
return price;
}
}
/**
* 抽象装饰类
*/
public abstract class CarDecorator implements Car{
private Car car;
public CarDecorator(Car car){
this.car = car;
}
@Override
public String getName() {
return car.getName();
}
@Override
public int getPrice() {
return car.getPrice();
}
}
/**
* 具体的装饰类
*/
public class LoudspeakerCarDecorator extends CarDecorator{
private String name = "加一个喇叭";
private int price = 1000;
public LoudspeakerCarDecorator(Car car){
super(car);
}
@Override
public String getName() {
return super.getName() + "," + name;
}
/**
* 1000 是喇叭的价格
* @return
*/
@Override
public int getPrice() {
return super.getPrice() + price;
}
}
/**
* 具体的装饰类
*/
public class SearchlightCarDecorator extends CarDecorator{
private String name = "加一个探照灯";
private int price = 2000;
public SearchlightCarDecorator(Car car){
super(car);
}
@Override
public String getName() {
return super.getName() + "," + name;
}
/**
* 2000 是探照灯的价格
* @return
*/
@Override
public int getPrice() {
return super.getPrice() + price;
}
}
public class Test {
public static void main(String[] args) {
// 选个大汽车
Car bigCar = new BigCar();
// 加个喇叭
bigCar = new LoudspeakerCarDecorator(bigCar);
// 加个探照灯
bigCar = new SearchlightCarDecorator(bigCar);
// 加个探照灯
bigCar = new SearchlightCarDecorator(bigCar);
System.out.println(bigCar.getName() + ",价格:" + bigCar.getPrice());
}
}
优点:
装饰模式与继承关系的目的都是要扩展对象的功能,但装饰模式可以提供比继承更多的灵活性。
使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无须改变,符合“开闭原则”。
缺点:
装饰模式增加了许多子类,如果过度使用会使程序变得很复杂 (多层包装)。
装饰器模式和桥接模式对比
相同点都是通过封装其他对象达到设计的目的,和对象适配器也类似,有时也叫半装饰设计模式。
没有装饰者和被装饰者的主次区别,桥接和被桥接者是平等的,桥接可以互换,不用继承自同一个父类,比如例子里面的,可以是Phone持有Color,也可以是Color持有Phone。
桥接模式不用使用同一个接口,装饰模式用同一个接口装饰,接口在父类中定义。
9、代理模式
代码实现:
/**
* 通用功能
*/
public interface SellPhone {
void sell();
}
/**
* 真实对象
*/
public class SellApplePhone implements SellPhone{
@Override
public void sell() {
System.out.println("卖苹果手机");
}
}
/**
* 代理对象,增强功能
*/
public class SellApplePhoneProxy implements SellPhone{
private SellPhone sellApplePhone = new SellApplePhone();
@Override
public void sell() {
chooseAddress();
sellApplePhone.sell();
putUpAdvertisement();
}
private void chooseAddress(){
System.out.println("选一个人流量很高的地址");
}
private void putUpAdvertisement(){
System.out.println("贴广告");
}
}
public class Test {
public static void main(String[] args) {
// 真实对象的行为
SellPhone sellApplePhone = new SellApplePhone();
sellApplePhone.sell();
// 代理对象的行为
SellPhone sellApplePhoneProxy = new SellApplePhoneProxy();
sellApplePhoneProxy.sell();
}
}
优点:
可以在访问一个类时做一些控制,或增加功能。
操作代理类无须修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
增加系统复杂性和调用链路。
代理模式和装饰器模式的区别:
代理模式主要是两个功能:
保护目标对象。
增强目标对象,和装饰模式类似。
10、外观模式
门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
定义了一个高层接口,这个接口使得系统更加容易使用。
代码实现:
public interface IMessage {
void pushMessage();
}
public class WechatMessage implements IMessage{
@Override
public void pushMessage() {
System.out.println("推送微信消息");
}
}
public class SmsMessage implements IMessage{
@Override
public void pushMessage() {
System.out.println("推送短信消息");
}
}
public class MailMessage implements IMessage{
@Override
public void pushMessage() {
System.out.println("推送邮件消息");
}
}
public class MessageFacade implements IMessage{
private WechatMessage wechatMessage = new WechatMessage();
private SmsMessage smsMessage = new SmsMessage();
private MailMessage mailMessage = new MailMessage();
@Override
public void pushMessage() {
wechatMessage.pushMessage();
smsMessage.pushMessage();
mailMessage.pushMessage();
}
}
public class Test {
public static void main(String[] args) {
IMessage iMessage = new MessageFacade();
iMessage.pushMessage();
}
}
优点:
减少了系统的相互依赖,提高了灵活性。
符合依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体。
符合迪米特法则:最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用。
缺点:
增加了系统的类和链路。
不是很符合开闭原则,如果增加新的逻辑,需要修改facade外观类。
11、享元模式
属于结构型模式,主要用于减少创建对象的数量,以减少内存占用和提高性能, 它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
内部状态:
不会随环境的改变而有所不同,是可以共享的。
外部状态:
不可以共享的,它随环境的改变而改变,因此外部状态是由客户端来保持(因为环境的变化一般是由客户端引起的)。
角色:
抽象享元角色:为具体享元角色规定了必须实现的方法,而外部状态就是以参数的形式通过此方法传入。
具体享元角色:实现抽象角色规定的方法。如果存在内部状态,就负责为内部状态提供存储空间。
享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外部状态。
代码实现:
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public abstract class Website {
public abstract void run(Company company);
}
public class ConcreteWebsite extends Website{
private String category;
public ConcreteWebsite() {
}
public ConcreteWebsite(String category) {
this.category = category;
}
@Override
public void run(Company company) {
System.out.println("公司名称:" + company.getName() + ",网站分类:" + category);
}
}
public class WebsiteFactory {
private Map<String, ConcreteWebsite> map = new HashMap<>();
/**
* 根据分类获取网站
* @param category
* @return
*/
public Website getWebsiteByCategory(String category){
if(map.containsKey(category)){
return map.get(category);
}else {
ConcreteWebsite concreteWebsite = new ConcreteWebsite(category);
map.put(category, concreteWebsite);
return concreteWebsite;
}
}
/**
* 获取网站分类总数
* @return
*/
public int getWebsiteCategorySize(){
return map.size();
}
}
public class Test {
public static void main(String[] args) {
WebsiteFactory websiteFactory = new WebsiteFactory();
Website website = websiteFactory.getWebsiteByCategory("企业官网");
website.run(new Company("华为"));
Website website2 = websiteFactory.getWebsiteByCategory("企业官网");
website2.run(new Company("苹果"));
System.out.println("网站分类总数:" + websiteFactory.getWebsiteCategorySize());
}
}
12、策略模式
定义一系列的算法,把它们⼀一个个封装起来, 并且使它们可相互替换。
淘宝天猫双⼗十⼀一,正在搞活动有打折的、有满减的、有返利利的等等,这些算法只是⼀一种策略略,并且是随时都可能互相替换的, 我们就可以定义⼀一组算法,将每个算法都封装起来,并且使它们之间可以互换。
角色:
Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性。
ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法。
代码实现:
public class Order {
private int userId;
private int productId;
private double price;
public Order(int userId, int productId, double price) {
this.userId = userId;
this.productId = productId;
this.price = price;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
}
public abstract class Strategy {
/**
* 根据订单对象计算商品活动后的价格
* @param order
* @return
*/
public abstract double computePrice(Order order);
}
/**
* 策略上下文的封装
*/
public class PromotionContext {
private Strategy strategy;
public PromotionContext(Strategy strategy) {
this.strategy = strategy;
}
public double executeStrategy(Order order){
return strategy.computePrice(order);
}
}
public class NormalActivity extends Strategy{
@Override
public double computePrice(Order order) {
return order.getPrice();
}
}
public class DiscountActivity extends Strategy{
/**
* 折扣
*/
private double rate;
public DiscountActivity(double rate) {
this.rate = rate;
}
@Override
public double computePrice(Order order) {
return order.getPrice() * rate;
}
}
public class VoucherActivity extends Strategy{
/**
* 优惠券
*/
private double voucher;
public VoucherActivity(double voucher) {
this.voucher = voucher;
}
@Override
public double computePrice(Order order) {
if(order.getPrice() > voucher){
return order.getPrice() - voucher;
}else {
return 0;
}
}
}
public class Test {
public static void main(String[] args) {
Order order = new Order(1, 1, 100);
// 不同策略计算出不同价格
PromotionContext context = new PromotionContext(new NormalActivity());
double normalPrice = context.executeStrategy(order);
context = new PromotionContext(new DiscountActivity(0.5));
double discountPrice = context.executeStrategy(order);
context = new PromotionContext(new VoucherActivity(60));
double voucherPrice = context.executeStrategy(order);
System.out.println("普通价格:" + normalPrice + ",折扣价格:" + discountPrice + ",使用优惠券价格:" + voucherPrice);
}
}
优点:
满⾜足开闭原则,当增加新的具体策略略时,不不需要修改上下⽂文类的代码,上下⽂文就可以引⽤用新的具体策略略的实例例。
避免使⽤用多重条件判断,如果不不⽤用策略略模式可能会使⽤用多重条件语句句不不利利于维护,和⼯工⼚厂模式的搭配使⽤用可以很好地消除代码if-else的多层嵌套(⼯工⼚厂模式主要是根据参数,获取不不同的策略略)。
缺点:
策略类数量会增多,每个策略都是一个类,复用的可能性很小。
对外暴露了类所有的行为和算法,行为过多导致策略类膨胀。
13、模板方法模式
定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,属于行为型模式。
角色:
抽象模板(Abstract Template): 定义一个模板方法,这个模板方法一般是一个具体方法,给出一个顶级算法骨架,而逻辑骨架的组成步骤在相应的抽象操作中,推迟到子类实现。
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法:是整个算法中的一个步骤,包括抽象方法和具体方法。
抽象方法:在抽象类中声明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
具体模板(Concrete Template):实现父类所定义的一个或多个抽象方法,它们是一个顶级算法逻辑的组成步骤。
需求背景:
一个项目的生命周期:需求评审-设计-开发-测试-上线-运维。整个周期里面,需求评审-设计是固定的操作,而其他步骤流程耗时是根据项目来定的。
因此梳理了一个模板,来规范化项目,只管核心步骤和项目里程碑产出的结果,具体的工时安排和开发就让团队成员去操作。
代码实现:
public abstract class AbstractProject {
/**
* 定义模板方法,声明 final 防止子类覆盖更改顺序,流程统一复用
*/
public final void processProject(){
review();
design();
coding();
test();
online();
}
public void review(){
System.out.println("项目需求评审");
}
public void design(){
System.out.println("项目UI设计");
}
/**
* 抽象方法,有具体子类去实现,编码耗时不一样
*/
public abstract void coding();
/**
* 抽象方法,有具体子类去实现,测试有多种,自动化测试、压力测试、安全测试、手工测试
*/
public abstract void test();
/**
* 抽象方法,有具体子类去实现,上线可以全量发布、灰度发布、停机发布
*/
public abstract void online();
}
public class PayProject extends AbstractProject{
@Override
public void coding() {
System.out.println("开发耗时30天");
}
@Override
public void test() {
System.out.println("安全测试");
}
@Override
public void online() {
System.out.println("全量发布");
}
}
public class UserProject extends AbstractProject{
@Override
public void coding() {
System.out.println("开发耗时60天");
}
@Override
public void test() {
System.out.println("压力测试");
}
@Override
public void online() {
System.out.println("停机发布");
}
}
public class Test {
public static void main(String[] args) {
PayProject payProject = new PayProject();
payProject.processProject();
System.out.println("================");
UserProject userProject = new UserProject();
userProject.processProject();
}
}
优点:
扩展性好,对不变的代码进行封装,对可变的进行扩展,符合开闭原则。
提高代码复用性 将相同部分的代码放在抽象的父类中,将不同的代码放入不同的子类中。
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,会使系统变得复杂。
14、观察者模式
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新,也叫做发布订阅模式Publish/Subscribe,属于行为型模式。
应用场景:
消息通知里面:邮件通知、广播通知、微信朋友圈、微博私信等,就是监听观察事件。
当一个对象的改变需要同时改变其它对象,且它不知道具体有多少对象有待改变的时候,考虑使用观察者模式。
角色:
Subject主题:持有多个观察者对象的引用,抽象主题提供了⼀一个接口可以增加和删除观察者对象;有一个观察者数组,并实现增、删及通知操作。
Observer抽象观察者:为具体观察者定义一个接口,在得到主题的通知时更新自己。
ConcreteSubject具体主题:将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知。
ConcreteObserver具体观察者:实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态保持一致。
代码实现:
public interface Observer {
void update();
}
public class Subject {
private List<Observer> observerList = new ArrayList<>();
public void addObserver(Observer observer){
this.observerList.add(observer);
}
public void deleteObserver(Observer observer){
this.observerList.remove(observer);
}
public void notifyAllObserver(){
for (Observer observer : this.observerList) {
observer.update();
}
}
}
public class BossConcreteSubject extends Subject{
public void doSomething(){
System.out.println("老板视察公司工作情况");
super.notifyAllObserver();
}
}
public class ZhangSanConcreteObserver implements Observer{
@Override
public void update() {
System.out.println("张三发现领导来视察工作,暂停摸鱼,认真工作");
}
}
public class LiSiConcreteObserver implements Observer{
@Override
public void update() {
System.out.println("李四发现领导来视察工作,暂停摸鱼,认真工作");
}
}
public class Test {
public static void main(String[] args) {
// 创建一个主题,老板
BossConcreteSubject bossConcreteSubject = new BossConcreteSubject();
// 创建观察者,就是摸鱼的同事
ZhangSanConcreteObserver zhangSanConcreteObserver = new ZhangSanConcreteObserver();
LiSiConcreteObserver liSiConcreteObserver = new LiSiConcreteObserver();
// 建立对应关系,老板这个主题被同事进行观察
bossConcreteSubject.addObserver(zhangSanConcreteObserver);
bossConcreteSubject.addObserver(liSiConcreteObserver);
// 主题开始活动,会通知观察者,相当于发布消息
bossConcreteSubject.doSomething();
}
}
优点:
降低了目标与观察者之间的耦合关系,目标与观察者之间建立了一套触发机制。
观察者和被观察者是抽象耦合的。
缺点:
观察者和观察目标之间有循环依赖的话,会触发它们之间进行循环调用,可能导致系统崩溃。
一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
15、责任链模式
客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。
让多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条调用链,并沿着这条链传递该请求,直到有一个对象处理它才终止。
有两个核心行为:一是处理请求,二是将请求传递到下一节点。
角色:
Handler抽象处理者:定义了一个处理请求的接口。
ConcreteHandler具体处理者: 处理所负责的请求,可访问它的后续节点,如果可处理该请求就处理,否则就将该请求转发给它的后续节点。
代码实现:
public class Request {
private String requestType;
private int money;
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
public abstract class Risk {
protected String name;
protected Risk superior;
public Risk(String name) {
this.name = name;
}
public void setSuperior(Risk superior) {
this.superior = superior;
}
public abstract void handlerRequest(Request request);
}
public class FirstRisk extends Risk{
public FirstRisk(String name) {
super(name);
}
@Override
public void handlerRequest(Request request) {
if(StringUtils.isNotBlank(request.getRequestType())
&& request.getMoney() <= 1000){
System.out.println("低风险操作,输入支付密码");
System.out.println(name + ":" + request.getRequestType() +
",金额:" + request.getMoney() + "处理完成");
}else {
// 下个节点进行处理
if(superior != null){
superior.handlerRequest(request);
}
}
}
}
public class SecondRisk extends Risk{
public SecondRisk(String name) {
super(name);
}
@Override
public void handlerRequest(Request request) {
if(StringUtils.isNotBlank(request.getRequestType())
&& request.getMoney() > 1000 && request.getMoney() <= 10000){
System.out.println("中风险操作,输入支付密码+短信验证码");
System.out.println(name + ":" + request.getRequestType() +
",金额:" + request.getMoney() + "处理完成");
}else {
// 下个节点进行处理
if(superior != null){
superior.handlerRequest(request);
}
}
}
}
public class ThirdRisk extends Risk{
public ThirdRisk(String name) {
super(name);
}
@Override
public void handlerRequest(Request request) {
if(StringUtils.isNotBlank(request.getRequestType())
&& request.getMoney() > 10000){
System.out.println("高风险操作,输入支付密码+短信验证码+人脸识别");
System.out.println(name + ":" + request.getRequestType() +
",金额:" + request.getMoney() + "处理完成");
}else {
// 下个节点进行处理
if(superior != null){
superior.handlerRequest(request);
}
}
}
}
public class Test {
public static void main(String[] args) {
FirstRisk firstRisk = new FirstRisk("初级风控");
SecondRisk secondRisk = new SecondRisk("中级风控");
ThirdRisk thirdRisk = new ThirdRisk("高级风控");
firstRisk.setSuperior(secondRisk);
secondRisk.setSuperior(thirdRisk);
Request request = new Request();
request.setRequestType("转账");
request.setMoney(1000);
firstRisk.handlerRequest(request);
request.setMoney(10000);
firstRisk.handlerRequest(request);
request.setMoney(100000);
firstRisk.handlerRequest(request);
}
}
优点:
客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递,所以责任链将请求的发送者和请求的处理者降低了耦合度。
通过改变链内的调动它们的次序,允许动态地新增或者删除处理类,比较方便维护。
增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则。
每个类只需要处理自己该处理的工作,明确各类的责任范围,满足单一职责原则。
缺点:
处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
不能保证请求一定被接收。
如果链路比较长,系统性能将受到一定影响,而且在进行代码调试时不太方便。
16、命令模式
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的对象,并把该命令传给相应的对象执行命令,属于行为型模式。
命令模式是一种特殊的策略模式,体现的是多个策略执行的问题,而不是选择的问题。
角色:
抽象命令(Command):需要执行的所有命令都在这里声明。
具体命令(ConcreteCommand):定义一个接收者和行为之间的弱耦合,实现execute()方法,负责调用接收者的相应操作,execute()方法通常叫做执行方法。
接受者(Receiver):负责具体实施和执行一个请求,干活的角色,命令传递到这里是应该被执行的,实施和执行请求的方法叫做行动方法。
请求者(Invoker):负责调用命令对象执行请求,相关的方法叫做行动方法。
客户端(Client):创建一个具体命令(ConcreteCommand)对象并确定其接收者。
代码实现:
public class ConditionReceiver {
public void on(){
System.out.println("空调开启");
}
public void off(){
System.out.println("空调关闭");
}
public void cool(){
System.out.println("空调制冷");
}
public void warm(){
System.out.println("空调制热");
}
}
public interface Command {
/**
* 执行动作
*/
void execute();
}
public class OnCommand implements Command{
/**
* 对哪个receiver进行处理
*/
private ConditionReceiver receiver;
public OnCommand(ConditionReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.on();
}
}
public class OffCommand implements Command{
private ConditionReceiver receiver;
public OffCommand(ConditionReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.off();
}
}
public class CoolCommand implements Command{
private ConditionReceiver receiver;
public CoolCommand(ConditionReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.cool();
}
}
public class WarmCommand implements Command{
private ConditionReceiver receiver;
public WarmCommand(ConditionReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.warm();
}
}
public class AppInvoker {
private Command onCommand;
private Command offCommand;
private Command coolCommand;
private Command warmCommand;
public void setOnCommand(Command onCommand) {
this.onCommand = onCommand;
}
public void setOffCommand(Command offCommand) {
this.offCommand = offCommand;
}
public void setCoolCommand(Command coolCommand) {
this.coolCommand = coolCommand;
}
public void setWarmCommand(Command warmCommand) {
this.warmCommand = warmCommand;
}
public void on(){
onCommand.execute();
}
public void off(){
offCommand.execute();
}
public void cool(){
coolCommand.execute();
}
public void warm(){
warmCommand.execute();
}
}
public class Test {
public static void main(String[] args) {
// 创建接受者,空调就是接受者
ConditionReceiver receiver = new ConditionReceiver();
// 创建命令对象,设置命令的接受者
OnCommand onCommand = new OnCommand(receiver);
OffCommand offCommand = new OffCommand(receiver);
CoolCommand coolCommand = new CoolCommand(receiver);
WarmCommand warmCommand = new WarmCommand(receiver);
// 创建请求者,把命令对象设置进去,App就是请求发起者
AppInvoker appInvoker = new AppInvoker();
appInvoker.setOnCommand(onCommand);
appInvoker.setOffCommand(offCommand);
appInvoker.setCoolCommand(coolCommand);
appInvoker.setWarmCommand(warmCommand);
appInvoker.on();
appInvoker.off();
appInvoker.cool();
appInvoker.warm();
}
}
优点:
调用者角色与接收者角色之间没有任何依赖关系,不需要了解到底是哪个接收者执行,降低了系统耦合度。
扩展性强,新的命令可以很容易添加到系统中去。
缺点:
过多的命令模式会导致某些系统有过多的具体命令类。
17、迭代器模式
提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部实现,属于行为型模式。
提到迭代器,想到它是与集合相关的,集合也叫容器,可以将集合看成是一个可以包容对象的容器,例如List,Set,Map,甚至数组都可以叫做集合,迭代器的作用就是把容器中的对象一个一个地遍历出来。
角色:
抽象容器(Aggregate):提供创建具体迭代器角色的接口,一般是接口,包括一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
具体容器角色(ConcreteAggregate):实现抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkedList, Set接口的哈希列表的实现HashSet等。
抽象迭代器角色(Iterator):负责定义访问和遍历元素的接口,包括几个核心方法,取得下一个元素的方法next(),判断是否遍历结束的方法isDone()(或者叫hasNext()),移除当前对象的方法remove()。
具体迭代器角色(ConcreteIterator):实现迭代器接口中定义的方法,并要记录遍历中的当前位置,完成集合的迭代。
代码实现:
/**
* 抽象迭代器
*/
public interface Iterator {
/**
* 获取下个元素
* @return
*/
Object next();
/**
* 是否有下一个
* @return
*/
boolean hasNext();
/**
* 删除元素
* @param obj
* @return
*/
Object remove(Object obj);
}
public class ConcreteIterator implements Iterator{
private List list;
private int index = 0;
public ConcreteIterator(List list) {
this.list = list;
}
@Override
public Object next() {
Object obj = null;
if(this.hasNext()){
obj = this.list.get(index);
index++;
}
return obj;
}
@Override
public boolean hasNext() {
if(index == list.size()){
return false;
}
return true;
}
@Override
public Object remove(Object obj) {
return list.remove(obj);
}
}
/**
* 抽象容器
*/
public interface ICollection {
void add(Object obj);
void remove(Object obj);
Iterator iterator();
}
public class MyCollection implements ICollection{
private List list = new ArrayList();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(list);
}
}
public class Test {
public static void main(String[] args) {
ICollection collection = new MyCollection();
collection.add("张三");
collection.add("李四");
collection.add("王五");
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
}
优点:
可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
支持以不同的方式遍历一个聚合对象。
缺点:
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。
18、备忘录模式
在不破坏封闭的前提下,捕获一个对象的内部状态,保存对象的某个状态,以便在适当的时候恢复对象,又叫做快照模式,属于行为模式。
备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问。
应用场景:
游戏的存档功能。
角色:
Originator: 发起者,记录当前的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态所需的所有数据,可以根据需要决定备忘录存储自己的哪些内部状态。
Memorandum:备忘录,负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
Caretaker: 管理者,对备忘录进行管理、保存和提供备忘录,只能将备忘录传递给其他角色。
Originator 和 Memorandum属性类似。
代码实现:
/**
* 快照记录
*/
public class RoleStateMemorandum {
/**
* 生命力会下降
*/
private int live;
/**
* 攻击力会上涨
*/
private int attack;
public RoleStateMemorandum(int live, int attack) {
this.live = live;
this.attack = attack;
}
public int getLive() {
return live;
}
public void setLive(int live) {
this.live = live;
}
public int getAttack() {
return attack;
}
public void setAttack(int attack) {
this.attack = attack;
}
}
/**
* 发起者
*/
public class RoleOriginator {
/**
* 生命力会下降
*/
private int live = 100;
/**
* 攻击力会上涨
*/
private int attack = 50;
public void display(){
System.out.println("开始============");
System.out.println("生命力" + live);
System.out.println("攻击力" + attack);
System.out.println("结束============");
}
/**
* 打架
*/
public void fight(){
// 生命力下降
this.live = live - 10;
// 攻击力增加
this.attack = attack + 10;
}
/**
* 保存快照,存储状态
* @return
*/
public RoleStateMemorandum saveState(){
return new RoleStateMemorandum(live, attack);
}
/**
* 恢复状态
* @return
*/
public void recoveryState(RoleStateMemorandum roleStateMemorandum){
this.live = roleStateMemorandum.getLive();
this.attack = roleStateMemorandum.getAttack();
}
public int getLive() {
return live;
}
public void setLive(int live) {
this.live = live;
}
public int getAttack() {
return attack;
}
public void setAttack(int attack) {
this.attack = attack;
}
}
/**
* 管理者
*/
public class RoleStateCaretaker {
private RoleStateMemorandum roleStateMemorandum;
public RoleStateMemorandum getRoleStateMemorandum() {
return roleStateMemorandum;
}
public void setRoleStateMemorandum(RoleStateMemorandum roleStateMemorandum) {
this.roleStateMemorandum = roleStateMemorandum;
}
}
public class Test {
public static void main(String[] args) {
RoleOriginator roleOriginator = new RoleOriginator();
roleOriginator.display();
roleOriginator.fight();
roleOriginator.display();
System.out.println("保存上面快照");
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemorandum(roleOriginator.saveState());
roleOriginator.fight();
roleOriginator.fight();
roleOriginator.display();
System.out.println("恢复快照");
roleOriginator.recoveryState(roleStateCaretaker.getRoleStateMemorandum());
roleOriginator.display();
}
}
优点:
给用户提供了一种可以恢复状态的机制。
实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:
消耗更多的资源,而且每一次保存都会消耗一定的内存。
19、状态模式
对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为 ,属于行为型模式。
允许一个对象在其内部状态改变时改变它的行为。
状态模式是策略模式的孪生兄弟,它们的UML图是一样的,但实际上解决的是不同情况的两种场景问题。
应用场景:
一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
代码中包含大量与对象状态有关的条件语句 ,比如一个操作中含有庞大的多分支的条件if else语句,且这些分支依赖于该对象的状态。
电商订单状态:未支付、已支付、派送中,收货完成等状态,各个状态下处理不同的事情。
角色:
Context 上下文: 定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理。
State 抽象状态类: 定义一个接口来封装与Context的一个特定状态相关的行为。
ConcreteState具体状态类: 实现抽象状态定义的接口。
代码实现:
public interface State {
void handle();
}
public class NewOrderState implements State{
@Override
public void handle() {
System.out.println("新订单,未支付");
System.out.println("调用商户客服服务,有新订单\n");
}
}
public class PayOrderState implements State{
@Override
public void handle() {
System.out.println("新订单已经支付");
System.out.println("调用商户客服服务,订单已经支付");
System.out.println("调用物流服务,未发货\n");
}
}
public class SendOrderState implements State{
@Override
public void handle() {
System.out.println("订单已经发货");
System.out.println("调用短信服务,通知用户已经发货");
System.out.println("更新物流信息\n");
}
}
public class OrderContext {
private State state;
public void setState(State state) {
this.state = state;
System.out.println("订单状态变更");
this.state.handle();
}
}
public class Test {
public static void main(String[] args) {
OrderContext orderContext = new OrderContext();
orderContext.setState(new NewOrderState());
orderContext.setState(new PayOrderState());
orderContext.setState(new SendOrderState());
}
}
优点:
只需要改变对象状态即可改变对象的行为。
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
状态模式的使用会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码。
状态模式和策略模式的区别
UML图⼀一样,结构基本类似。
状态模式重点在各状态之间的切换,从而做不同的事情。
策略模式更侧重于根据具体情况选择策略,并不涉及切换。
状态模式不同状态下做的事情不同,而策略模式做的都是同一件事。例如,聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付。
状态模式,各个状态的同一方法做的是不同的事,不能互相替换。