文章目录
二十三种设计模式
设计模式总共有三大类,二十三种:
- 创建型模式(五种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式(七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
- 行为型模式(十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
1. 创建型模式
1.简单工厂模式(不属于GOF23种设计模式之中)
此模式虽然不属于23种设计模式之内,但是其应用十分广泛,是一种经典的设计思想,也在此处进行简单介绍
总体实现思路:
创建父类包含公共部分,由各子类进行继承,创建一个生成实例的类,通过方法入参,匹配符合条件的子类,并创建子类的实例,返回给调用者
-
创建父类
public class Animal { }
-
创建子类,继承Animal
class Cat extends Animal{ public Cat() { System.out.println("创建了猫类"); } }
class Dog extends Animal{ public Dog() { System.out.println("创建了狗类"); } }
-
创建工厂类
class AnimalFactory { public Animal create(String type) { switch (type){ case "cat": return new Cat(); case "dog": return new Dog(); default: return null; } } }
-
测试方法
void test() { AnimalFactory anim = new AnimalFactory(); Animal Cat = anim.create("cat"); Animal Dog = anim.create("dog"); }
简单工厂模式的优缺点
优点是结构简单,提高了系统的灵活性
缺点是不符合‘开闭原则’,每次需要添加类,都需要修改工厂,而且工厂一旦出现问题,将造成整个系统的瘫痪,针对上述问题,提出了工厂方法模式
开闭原则:在面向对象编程领域,对于扩展是开放的,对于修改时封闭的
2. 工厂方法模式
实现思路,对每个类都由对应的工厂,这样添加新类型的时候,只需要添加子类,然后添加工厂类,实现工厂接口,在获得实例的时候,首先实例化工厂实现类,通过工厂实现类中的方法获得实例
-
创建父类
public class Animal { }
-
创建子类
public class Cat extends Animal{ public Cat() { System.out.println("创建了猫类"); } }
public class Dog extends Animal{ public Dog() { System.out.println("创建了狗类"); } }
-
创建工厂接口
public interface AnimalFactory { Animal create(); }
-
创建工厂实现类
public class CatFactory implements AnimalFactory{ @Override public Cat create() { return new Cat(); } }
public class DogFactory implements AnimalFactory{ @Override public Dog create() { return new Dog(); } }
-
测试方法
void test() { DogFactory dogFactory = new DogFactory(); Dog dog = dogFactory.create(); CatFactory catFactory1 = new CatFactory(); Cat cat = catFactory1.create(); }
此种方式的优点非常明显,也符合开闭原则,但是缺点也同样很明显,添加一个类,系统中的类将成倍增加,从一定层面来说增加了系统的复杂度,也增加了系统的依赖性
3. 抽象工厂模式
-
创建父子类
public class Cat { } public class CatA extends Cat { public CatA() { System.out.println("创建了猫A类"); } } public class CatB extends Cat{ public CatB() { System.out.println("创建了猫B类"); } }
public class Dog { } public class DogA extends Dog{ public DogA() { System.out.println("创建了狗A类"); } } public class DogB extends Dog{ public DogB() { System.out.println("创建了狗B类"); } }
-
创建工厂类
public interface PetFactory { public Dog createDog(); public Cat createCat(); }
public class PetFactoryA implements PetFactory { @Override public Dog createDog() { return new DogA(); } @Override public Cat createCat() { return new CatA(); } }
public class PetFactoryB implements PetFactory { @Override public Dog createDog() { return new DogB(); } @Override public Cat createCat() { return new CatB(); } }
-
创建测试类
void test() { PetFactoryA petFactory = new PetFactoryA(); Dog dog = petFactory.createDog(); Cat cat = petFactory.createCat(); PetFactoryB petFactoryB = new PetFactoryB(); Cat cat1 = petFactoryB.createCat(); Dog dog1 = petFactoryB.createDog(); }
总体的实现思路与工厂方法一致,但是与工厂方法不同的是,工厂方法返回一个产品类,而抽象工程返回多个产品类,本质上可以从由谁决定产品类来区分,如果由调用类来决定,创建了哪个产品类,则是工厂方法,如果由工厂类来决定,则是抽象工厂
4. 单例模式
确保一个类只有一个实例,并提供一个全局访问点
单例模式分为预加载和懒加载(饿汉模式和懒汉模式),预加载是在类加载的时候,就会创建实例,懒加载只有在用到的时候才会创建实例,相对来说,预加载可能会造成资源的浪费,但是预加载没有线程安全的问题,懒加载存在线程安全的问题,我们会对懒加载的线程安全问题做一下改进
下面对懒加载和预加载做示例
-
预加载
public class Singleton { private static Singleton test = new Singleton(); private Singleton() { } public static Singleton getSingleton(){ return test; } }
-
懒加载
public class Singleton { private static Singleton test = null; private Singleton() { } public static Singleton getTest(){ if (test == null){ test = new Singleton(); return test; } return test; } }
-
改进懒加载,解决线程安全问题,在获取示例的方法上添加synchronized
public static synchronized Singleton getSingleton(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (test == null){ test = new Singleton(); return test; } return test; }
5. 建造者模式
建造者模式又叫生成器模式是将一个复杂的对象的构建与表示分离,使用同样的构建过程创建不同的表示
实现思路
一般来说建造者模式有四个角色:
- 产品(product)
- 抽象建造者(Builder)
- 建造者(ConcreteBuilder)
- 指挥者(Director)
我们需要的是一个经过加工的产品,产品的组成由抽象建造者制定,具体的加工细节由建造者进行实施,指挥者可以通过不同的流程将需要的产品制作出来,就像我们需要一碗面条,抽象建造者就是对这碗面条的原料进行定义(水,面条,盐,鸡蛋),建造者对面条进行加工,但是如何加工就需要根据指挥者,先加水,然后是加面条,还是加盐,还是先加鸡蛋,简单来说,抽象建造者告诉你需要做什么,指挥者告诉你按什么顺序做
使用此种模式的优缺点:
- 优点:符合开闭原则,创建者和使用者分离,封装了创建方式和创建步骤,使用者不必了解复杂的创建过程,方便添加新的创建者,以控制生产出不同的产品
- 缺点:使用范围受到限制,要求各产品之间有很多的共同点,如果生成的产品的种类很庞大,则需要添加很多的创建者
使用代码进行实现
-
产品
public class Product { //原料A private String materialA; //原料B private String materialB; //原料C private String materialC; public String getMaterialA() { return materialA; } public void setMaterialA(String materialA) { this.materialA = materialA; } public String getMaterialB() { return materialB; } public void setMaterialB(String materialB) { this.materialB = materialB; } public String getMaterialC() { return materialC; } public void setMaterialC(String materialC) { this.materialC = materialC; } }
-
抽象建造者
public abstract class Builder { protected Product product = new Product(); //工序A public abstract void buildMaterialA(); //工序B public abstract void buildMaterialB(); //工序C public abstract void buildMaterialC(); public Product builderProduct() { return product; } }
-
建造者
public class ConcreteBuilder extends Builder{ @Override public void buildMaterialA() { super.product.setMaterialA("添加原料A"); System.out.println("添加原料A"); } @Override public void buildMaterialB() { super.product.setMaterialB("添加原料B"); System.out.println("添加原料B"); } @Override public void buildMaterialC() { super.product.setMaterialC("添加原料C"); System.out.println("添加原料C"); } }
-
指挥者
public class Director { Builder builder = null; public Director(Builder builder) { this.builder = builder; } public void setBuilder(Builder builder) { this.builder = builder; } public Product getProduct() { builder.buildMaterialA(); builder.buildMaterialB(); builder.buildMaterialC(); return builder.builderProduct(); } }
-
创建测试类
public void test2(){ ConcreteBuilder con = new ConcreteBuilder(); Product product = new Director(con).getProduct(); }
6. 原型模式
原型模式主要解决的问题是在创建对象的时候如果时间过长,则需要消耗大量的时间来创建对象,使用克隆模式可以很快的创建出对象,此方式需要实现Cloneable接口,并重写此接口内的clone()方法
原型模式包括深克隆和浅克隆,二者的区别主要是在要克隆类中如果属性是对象的话,那么浅克隆只能克隆对象的指针指向,并不会重新构建此对象,也就是说大家共享同一个对象属性,如果其中一个克隆者对此对象做出改变,则所有的克隆者的对象值都会改变,而深克隆则将此属性的对象重新创建,不存在共享的问题,在更改其中一个克隆者的属性对象的值的时候,其他的克隆者的属性对象值不会改变
深克隆相对于浅克隆的优缺点
优点:深克隆可以很好的隔离开各克隆对象,防止因为共享对象的问题造成数据错误
缺点:如果创建的对象很多,可能会消耗极大的资源,因为每个对象属性都要重新创建,而且对象属性如果出现循环引用可能出现死循环的问题
看代码示例
-
浅克隆创建类的时候需要实现Cloneable接口,并实现clone()方法
public class Email implements Cloneable{ private String id; private String name; private String EmailUrl; //深克隆和浅克隆的区别在此处 public User user ; public Email() { } @Override public Object clone(){ Email em = null; try { em = (Email)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return em; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmailUrl() { return EmailUrl; } public void setEmailUrl(String emailUrl) { EmailUrl = emailUrl; } }
-
深克隆创建对象的两种方式
-
将类中所有涉及的引用属性也进行浅克隆,此种方式缺点十分明显,引用属性中也有引用属性,则需要依次进行浅克隆,如果有子引用没有克隆到的,就会出现和浅克隆同样的问题
@Override public Object clone(){ Email em = null; try { em = (Email)super.clone(); Product product = em.product; Product clone = (Product)product.clone(); em.product = clone; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return em; }
-
使用流的方式进行克隆,此种方式也比较常用,要求引用的对象也要实现Serializable接口
public Object clone(){ ObjectInputStream ois = null; ObjectOutputStream oos = null; ByteArrayInputStream in = null; ByteArrayOutputStream out = null; Email o = null; try { //序列化 out = new ByteArrayOutputStream(); oos = new ObjectOutputStream(out); oos.writeObject(this); //反序列化 in = new ByteArrayInputStream(out.toByteArray()); ois = new ObjectInputStream(in); o = (Email)ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { out.close(); oos.close(); in.close(); ois.close(); } catch (IOException e) { e.printStackTrace(); } } return o; }
-
-
创建测试类
void test3(){ Email email1 = new Email(); Email email2 = (Email)email1.clone(); }
2. 结构型模式
1.适配器模式
当接口无法和类匹配到一起工作的时候,可以通过适配器将接口变换成可以和类匹配到一起,主要分为三种,类适配器,对象适配器,接口适配器,优缺点的对比
优点:对代码的复用性增强,对于目标接口和源代码差距不大的接口,适配器可以很好的进行融合,符合开闭原则,而且可以很好的对功能进行扩展
缺点:使用的多了会导致代码的逻辑性很容易混乱,调用的走向也很乱,可读性会比较差,如果目标接口和源代码的差距非常大的情况下,建议重构一下代码
主要分为三部分:
源(Adaptee):需要被适配的对象或者类型
适配器(Adapter):连接目标和源的中间对象
目标(Target):期待得到的目标
-
源数据
public class Adaptee { public void one(){ System.out.println("我是1"); } }
-
目标数据
public interface Target { void one(); void two(); }
-
类适配器,主要是通过继承原来的类,并实现客户需要的接口,实现客户需要添加的功能
public class Adapter extends Adaptee implements Target { @Override public void two() { System.out.println("我是two"); } }
-
对象适配器,主要是通过实现客户的接口,在接口中将原来代码实现的功能在客户重写的接口中重新执行,然后添加新增的接口
public class Adapter implements Target { Adaptee adaptee = new Adaptee(); @Override public void one() { adaptee.one(); } @Override public void two() { System.out.println("客户需要的方法"); } }
-
接口适配器,实现思路是对接口实现,对于默认实现的在抽象类中进行,将新增的功能在抽象类的子类中实现
public abstract class AbstractAdapter implements Target { public void one(){ System.out.println("我是默认实现"); } public abstract void two(); }
public class Adaptee { public void one(){ System.out.println("原来代码中的方法"); } }
-
测试类
void test4(){ Adapter adapter = new Adapter(); adapter.one(); adapter.two(); }
2. 装饰者模式
实现思路是通过创建一个装饰者对象,动态的扩展目标对象的功能,但是并不会破坏目标对象原来的结构,装饰着要与目标对象实现相同的接口,或者继承相同的抽象类,这种模式有四个角色,
- 被装饰者
- 装饰者实现类
- 装饰者
- 装饰者功能子类
整体的实现思路
我们有一个文具的商品接口(被装饰者),有具体的文具实现了此接口,比如铅笔(装饰者实现类)并返回了其中的价格,现在我们需要对文具类进行折扣处理,但是不改变原来的代码,而且折扣的方式有很多,我们提供一个抽象类(装饰者)负责将接口进行对接,然后由具体的折扣方式继承此装饰者
代码实现
-
创建一个文具接口
public interface Stationery { public double originalPrice(); }
-
创建一个具体的文具类(实现文具接口)
public class Pencil implements Stationery { @Override public double originalPrice() { return 4.00; } }
-
创建装饰者(实现文具接口)
public abstract class Discount implements Stationery{ protected Stationery stationery; public Discount(Stationery stationery) { this.stationery = stationery; } @Override public double originalPrice() { return stationery.originalPrice(); } }
-
创建装饰者子类
/** * 折扣实现九折 */ public class DiscountImpl extends Discount { public DiscountImpl(Stationery stationery) { super(stationery); } //对价格进行装饰 @Override public double originalPrice() { return super.originalPrice() * 0.9; } } /** * 满5元减1元 */ public class FullSubtraction extends Discount { public FullSubtraction(Stationery stationery) { super(stationery); } //对价格进行装饰 @Override public double originalPrice() { if (super.originalPrice() > 5.0){ return super.originalPrice() -1; } return super.originalPrice(); } }
-
创建测试类
void test5(){ Stationery stationery = new Pencil(); //输出原价 System.out.println(stationery.originalPrice()); //九折价格 stationery = new DiscountImpl(stationery); System.out.println(stationery.originalPrice()); //满减 stationery = new FullSubtraction(stationery); System.out.println(stationery.originalPrice()); }
3. 代理模式
代理模式分为静态代理和动态代理,代理模式主要功能是通过代理对目标对象进行控制访问以及功能增强
- 静态代理是在代理的时候已经知道要代理什么,然后对目标对象进行增强
- 动态代理弥补了静态代理的一些不足,比如说静态代理,如果出现了被代理对象有更改,则静态代理也需要更改,动态代理分为两种一种是jdk动态代理,另外一种是cglib动态代理,二者主要的区别是jdk需要实现接口,而cglib不需要实现接口
使用代码进行实现
-
创建目标对象
public interface UserService { String shopping(String paly); } public class UserServiceImpl implements UserService { @Override public String shopping(String play) { System.out.println(play); return play; } }
-
使用静态代理
public class StaticAgency implements UserService{ private UserService userService; public StaticAgency(UserService userService) { this.userService = userService; } @Override public String shopping(String play) { System.out.println("吃饭"); String shopping = userService.shopping(play); System.out.println("回家"); return shopping; } }
-
使用JDK动态代理
public class JDKAgency implements InvocationHandler { private Object object; public JDKAgency(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("看电影"); Object invoke = method.invoke(object, args); System.out.println("去海边"); return invoke; } }
-
使用cglib动态代理
public class CglibAgency implements MethodInterceptor { private Object object; public CglibAgency(Object object) { this.object = object; } //获取代理类的方法 public Object getProxy(){ //使用增强其返回代理对象 Enhancer enhancer = new Enhancer(); //设置对象的父类 enhancer.setSuperclass(object.getClass()); //添加此类 enhancer.setCallback(this); //返回创建的代理类 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("喝酒"); Object invoke = method.invoke(object, objects); return invoke; } }
-
创建测试类
//静态代理测试类 void test6(){ StaticAgency staticAgency = new StaticAgency(new UserServiceImpl()); staticAgency.shopping("买衣服"); } //JDK动态代理测试类 void test7(){ UserServiceImpl userServiceImpl = new UserServiceImpl(); InvocationHandler invoker = new JDKAgency(userServiceImpl); UserService proxy = (UserService)Proxy.newProxyInstance( //类加载器 userServiceImpl.getClass().getClassLoader(), //目标接口 userServiceImpl.getClass().getInterfaces(), //代理对象 invoker ); //执行 proxy.shopping("买衣服"); } //cglib代理测试类 void test8(){ CglibAgency cglibAgency = new CglibAgency(new UserServiceImpl()); UserService proxy = (UserService) cglibAgency.getProxy(); proxy.shopping("买衣服"); }
4. 外观模式
外观模式是将一系列复杂的操作,通过一个接口进行封装,可以让使用者不必了解背后的细节,从而降低复杂度,比如说我们购物,需要挑选商品,添加购物车,添加邮寄地址,结算等等过程,每一个过程都将设计一个调用,这对于使用者来说是很不友好的,我们可以通过一个接口将这一复杂的过程进行封装,只需要进行挑选和结算,但是缺点也很明显,不符合开闭原则,如果新增加一个子系统,就要对接口进行修改,下面使用代码演示
-
创建目标类,在实际应用中可能设计很多个接口,此处我们使用一个接口进行演示,假如一个完整的购物过程由以下几个步骤组成,每次购物都需要依次进行调用
public interface Shopping { //步骤1 挑选商品 void one(String commodity); //步骤2 将商品加入购物车 void two(String commodity); //步骤3 结算 void three(); //步骤4 添加地址 void four(); } public class ShoppingImpl implements Shopping { @Override public void one(String commodity) { System.out.println("挑选商品"+commodity); } @Override public void two(String commodity) { System.out.println("添加购物车"+commodity); } @Override public void three() { System.out.println("结算"); } @Override public void four() { System.out.println("添加地址"); } }
-
创建封装类,将过程封装,调用的时候只需要调用process即可
public class ShoppingAppearance { ShoppingImpl shoppingImpl = new ShoppingImpl(); public void process(String commodity){ shoppingImpl.one(commodity); shoppingImpl.two(commodity); shoppingImpl.three(); shoppingImpl.four(); } }
-
创建测试类
void test9(){ ShoppingAppearance shop = new ShoppingAppearance(); shop.process("笔记本"); }
5. 桥接模式
将抽象部分和实现部分进行分离,使他们都可以独立的变化,然后可以随意的组合
比如说手机,手机的品牌有很多,每个品牌又有白色和黑色手机,如果对每个类型都创建类,会造成类的激增,造成类爆炸,我们可以通过桥接的方式设计
代码实现
-
创建手机类,以后增加品牌,只需要增加品牌类
public abstract class Phon { Color color; public void setColor(Color color) { this.color = color; } public abstract void addColor(); } //小米 public class Xiaomi extends Phon { @Override public void addColor() { color.addColor(); System.out.println("小米"); } }
-
创建颜色类,以后增加的时候只需要增加颜色类
public abstract class Color { public abstract void addColor(); } //红色 public class Red extends Color { @Override public void addColor() { System.out.println("红色"); } }
-
创建测试类
void test10(){ Red red = new Red(); Xiaomi xiaomi = new Xiaomi(); xiaomi.setColor(red); xiaomi.addColor(); }
6. 组合模式
组合多个对象形成树形结构,以表示部分和整体之间的关系,组合模式对容器和叶子节点的处理有一致性,又称为整体-部分模式,结构分为抽象构件,树枝构件和树叶构件
优点:客户端可以一致的处理单个对象和组合对象,无需关心是处理的单个对象还是组合对象,节点可以随意增加,无需改源代码
缺点:叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则,设计比较复杂,而且不容限制容器中的构建
代码示例
-
创建抽象构件
public abstract class Component { protected abstract void add(Component c); protected abstract void remove(Component c); protected abstract Component getChildren(int i); protected abstract void operation(); }
-
创建树枝构件
public class Composite extends Component { private List<Component> list = new ArrayList<>(); @Override public void add(Component c) { list.add(c); } @Override public void remove(Component c) { list.remove(c); } @Override public Component getChildren(int i) { return list.get(i); } @Override public void operation() { for (Component component : list) { component.operation(); } } }
-
创建树叶构件
public class Leaf extends Component{ private String name; public Leaf(String name) { this.name = name; } @Override public void add(Component c) { } @Override public void remove(Component c) { } @Override public Component getChildren(int i) { return null; } @Override public void operation() { System.out.println(name); } }
7. 享元模式
如果在程序中又大量的对象创建,消耗内存资源,我们可以通过享元模式对对象共同的部分抽取出来,减少对象的创建
实现思路:我们通过创建一个map集合,集合的key是此属性的值,例如性别,像身份证号这样的就别做key了,除了此属性其他属性都是一致的,比如,男张三有五个,女张三有两个,在实际应用中我们需要创建八个对象,而使用享元模式只需要创建男张三和女张三就可以了
代码示例
-
创建对象
@Data @NoArgsConstructor @AllArgsConstructor public class Student { private String name; private String sex; public Student(String sex){ this.sex = sex; } }
-
创建对象工厂
public class StudentFactory { private static final HashMap<String,Student> map = new HashMap<>(); public static Student getStudent(String sex){ Student student = map.get(sex); if (student == null){ student = new Student("男"); map.put(sex,student); System.out.println("创建了一个性别为"+sex+"的学生"); } return student; } //此方法返回了集合,用来测试集合所占内存 public static HashMap<String,Student> getMap(){ return map; } }
-
创建测试类
public void test12(){ HashMap<String,Student> map1 = new HashMap<>(); for (int i = 0; i < 100; i++) { Student a = StudentFactory.getStudent("男"); map1.put(String.valueOf(i),a); a.setName("张三"); } HashMap<String, Student> map = StudentFactory.getMap(); //现在我们可以看下创建一百个学生和两个学生的集合占用内存差距有多少 System.out.println("未享元map数量:"+map1.size()); System.out.println("享元map数量:"+map.size()); System.out.println(RamUsageEstimator.sizeOf("未进行享元模式所占内存:"+map1)); System.out.println(RamUsageEstimator.humanSizeOf("未进行享元模式所占内存:"+map1)); System.out.println(RamUsageEstimator.sizeOf("未进行享元模式所占内存:"+map)); System.out.println(RamUsageEstimator.humanSizeOf("未进行享元模式所占内存:"+map)); }
3.行为型模式
1. 策略模式
策略模式定义了一系列算法, 将每个算法封装起来,使他们可以相互替换,算法的变化不会影响到调用者的使用,比如我们使用计算类模块,可以将计算定义为策略,每个计算方式定义为具体的策略,就可以通过添加策略类来实现扩展,符合开闭原则,并且对每个策略进行修改的时候,不会影响到其他的策略类
优点:算法可以自由切换,避免了使用多重的if判断,扩展性非常好
缺点:策略类增多,所有的策略类都会对外暴露
策略模式有四个角色:
- 抽象策略角色
- 具体策略角色
- 环境角色
代码示例
-
创建抽象策略角色
public interface Strategy { public int calc(int num1,int num2); }
-
创建具体策略角色
public class Add implements Strategy { @Override public int calc(int num1, int num2) { return num1+num2; } } public class Multiply implements Strategy { @Override public int calc(int num1, int num2) { return num1 * num2; } }
-
创建环境角色
public class Environment { private Strategy strategy; public Environment(Strategy strategy) { this.strategy = strategy; } public int calculate(int a,int b){ return strategy.calc(a,b); } }
-
创建测试类
public void test13(){ Add add = new Add(); Environment environment = new Environment(add); int calculate = environment.calculate(3, 4); System.out.println(calculate); }
2.模板模式
对于一些特性的事务或者场景,有特定的解决流程,将解决流程定义为模板,通过改变内容来实现不同的结果,比如说,我们炒菜,先放油,然后放菜,放调料,这是一个特定的流程,通过改变内容,可以实现炒出来一盘西红柿炒鸡蛋,也可以是一盘炒土豆丝
代码示例
-
创建模板类
public abstract class Dish { public void start(){ one(); two(); three(); } abstract void one(); abstract void two(); abstract void three(); }
-
创建实现类
public class FriedPotatoShreds extends Dish { @Override void one() { System.out.println("土豆丝"); } @Override void two() { System.out.println("青椒"); } @Override void three() { System.out.println("加入调料"); } } public class ScrambledEggsWithTomatoes extends Dish { @Override void one() { System.out.println("加入西红柿"); } @Override void two() { System.out.println("加入鸡蛋"); } @Override void three() { System.out.println("加入调料"); } }
-
创建测试类
public void test14() { Dish friedPotatoShreds = new FriedPotatoShreds(); friedPotatoShreds.start(); Dish scrambledEggsWithTomatoes = new FriedPotatoShreds(); scrambledEggsWithTomatoes.start(); }
3. 观察者模式
观察者模式的核心一对多的依赖关系,当被观察者发生了变化,观察者都能都收到通知
优点:观察者和被观察者抽象耦合,建立了一套通知机制
缺点:如果观察者和被观察者之间存在相互依赖,可能会造成崩溃,深层的观察者出现的话,会消耗时间对每一个观察者都通知到
代码示例
-
创建抽象观察者
public abstract class ObserverTest { public abstract void resp(); }
-
创建抽象被观察者
public abstract class Subject { //观察者列表 List<ObserverTest> list = new ArrayList<>(); //添加观察者 public void add(ObserverTest o){ list.add(o); }; //移除观察者 public void remove(ObserverTest o){ list.remove(o); }; //通知 public abstract void cry(); }
-
创建具体观察者
public class Dog extends ObserverTest { @Override public void resp() { System.out.println("狗跟着叫"); } } public class Cat extends ObserverTest { @Override public void resp() { System.out.println("猫跟着追"); } }
-
创建具体被观察者
public class Mouse extends Subject { @Override public void cry() { System.out.println("老鼠跑"); for (ObserverTest observer : list) { observer.resp(); } } }
-
创建测试类
public void test15(){ Mouse mouse = new Mouse(); ObserverTest cat = new com.example.demox.observer.Cat(); mouse.add(cat); mouse.add(new com.example.demox.observer.Dog()); mouse.cry(); }
4. 迭代器模式
迭代器模式主要是为了遍历一些集合,主要思想是通过使用判断集合是否为空,如果不为空,则此下标的值返回,主要的角色有迭代器抽象接口,具体迭代器,聚合接口,具体聚合,在迭代器接口中定义遍历聚合的使用的方法,具体迭代器对此方法需要实现的功能进行实现,聚合接口则是定义返回迭代器的方法
代码示例
-
创建迭代器接口
public interface Iterator { boolean hasNext(); Object next(); }
-
创建迭代器具体实现
public class IteratorImpl implements Iterator { private List<String> list; private int index = 0; public IteratorImpl(List<String> list) { this.list = list; } @Override public boolean hasNext() { if(list != null && list.size() > index){ return true; } return false; } @Override public Object next() { return list.get(index++); } }
-
创建聚合接口
public interface Aggregate { Iterator getIterator(); void add(String s); }
-
创建聚合具体实现
public class AggregateImpl implements Aggregate { List<String> list = new ArrayList<>(); @Override public Iterator getIterator() { return new IteratorImpl(list); } @Override public void add(String s) { list.add(s); } }
-
创建测试类
public void test16() { AggregateImpl aggregate = new AggregateImpl(); aggregate.add("zhangsan"); aggregate.add("zhangsan1"); aggregate.add("zhangsan2"); Iterator iterator = aggregate.getIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }
5. 责任链模式
责任链模式是请求者将请求发送过来,接收者进行处理,如果第一个接收者处理不了则交由下一个处理者,像链条一样向下传递,形成责任链,直到有一个接收者可以处理此请求
优点:请求者不必考虑具体的处理细节,将请求者和处理者解耦合
缺点:如果责任链形成闭环,且在设置条件的时候出现了逻辑漏洞,则可能出现死循环,造成系统崩溃
代码示例
-
创建指挥者
public abstract class Commander { Commander successor; public abstract void process(int i); public void sendSuccessor(Commander c){ this.successor = c; } }
-
创建处理者
public class One extends Commander { @Override public void process(int i) { if (i < 10){ System.out.println("one处理"+i); } else { successor.process(i); } } } public class Two extends Commander { @Override public void process(int i) { if (i >= 10){ System.out.println("two处理"+i); } else { successor.process(i); } } }
-
创建测试类
public void test17(){ One one = new One(); Two two = new Two(); one.sendSuccessor(two); one.process(30); }
6. 命令模式
命令模式是将请求封装成一个对象,将发送请求者和处理者分开,通过命令对象传递信息,这样方便将命令对象进行存储,传递,调用,增加,管理
命令模式是一个比较容易理解的结构,主要角色有四个
- 抽象命令
- 具体命令
- 请求者
- 接收者
代码示例
-
创建抽象命令接口
public interface Command{ void on(); void off(); }
-
创建具体命令类
public class CommandImpl implements Command { private Light light; public CommandImpl(Light light) { this.light = light; } @Override public void on() { light.onTelevision(); } @Override public void off() { light.offTelevision(); } }
-
创建接收者
public class Light { public void onTelevision(){ System.out.println("打开电视"); } public void offTelevision(){ System.out.println("关闭电视"); } }
-
创建发送者
public class Contral { public void onCommand(Command c){ c.on(); } public void offCommand(Command c){ c.off(); } }
-
创建测试类
public void test18(){ Light light = new Light(); Command command = new CommandImpl(light); command.on(); command.off(); }
7. 状态模式
一个事物会有多个状态,每个状态又对应一种行为,我们需要对状态的改变获取其要实施的行为,比如说水有液态和气态,我们将液态和气态抽象为接口,液态水要进行的操作和气态水要进行的操作抽象为行为方法,由液态要实施的操作和气态要实施的操作实现此接口,传入状态的实例,统一调用状态的行为方法,就可以实现传入不同的状态实现不同的操作,此种设计模式有三个角色
- 抽象状态角色(stat)
- 具体状态角色(ConcreteStat)
- 环境角色(Context)
代码示例
-
创建状态接口
public interface Stat { public void operation(); }
-
创建具体状态类
public class GasState implements Stat { @Override public void operation() { System.out.println("我是汽态水"); } } public class LiquidState implements Stat { @Override public void operation() { System.out.println("我是液态水"); } }
-
创建环境角色
public class Water { private Stat stat; public Water(Stat state) { this.stat = state; } public void handle() { stat.operation(); } }
-
创建测试类
public void test19(){ LiquidState liquidState = new LiquidState(); Water water = new Water(liquidState); water.handle(); }
8. 备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,将该状态保存,统一进行管理,在以后使用此对象的保存状态的时候可以随时使用,该行为也被称为快照模式,此模式有四个角色
- 备忘录
- 发起者
- 管理者
优点:提供了一种可以恢复状态的机制,对内部进行封装,对调用者开放,其他的屏蔽
缺点:如果备份的比较频繁的话,会占用比较大的资源
实现思路是,通过将此状态的对象传递给管理者,管理者将此状态的对象创建出来存入集合,在需要的时候,从集合中提取出来
-
创建备忘录
public class Memento { private String stat; public Memento(String stat) { this.stat = stat; } public String getStat() { return stat; } }
-
创建发起者
public class Originator { private String stat; public String getStat() { return stat; } public void setStat(String stat) { this.stat = stat; } public Memento saveMemento(){ return new Memento(stat); } public void restStatForMemento(Memento memento){ stat = memento.getStat(); } }
-
创建管理者
public class Caretaker { List<Memento> list = new ArrayList<>(); public void add(Memento memento){ list.add(memento); } public Memento getMemento(int index){ return list.get(index); } }
-
创建测试类
public void test20(){ Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setStat("1"); caretaker.add(originator.saveMemento()); System.out.println(originator.getStat()); originator.setStat("2"); System.out.println(originator.getStat()); originator.restStatForMemento(caretaker.getMemento(0)); System.out.println(originator.getStat()); }
9. 访问者模式
访问者模式封装了一些作用于数据结构的元素,在不改变内部结构的情况下,其他人可以操作这些元素,避免因为对元素的操作造成数据污染,比如说主人家养了宠物,有猫和狗,主人和朋友来喂猫喂狗,主人家就是对象结构,猫和狗属于具体元素,主人和朋友就是访问者,喂就是对元素的操作
优点:扩展性好,复用性好,灵活性好
缺点:新增比较困难,每添加一个具体角色,就要在每一个访问者中添加相应的操作,违反了依赖倒置的原则,访问者依赖了具体类,而不是依赖接口
有五个角色:
- 访问者抽象类,定义访问者要访问的元素
- 访问者具体角色,访问者进行的具体操作
- 抽象元素,定义对元素的操作
- 具体元素,定义对元素的具体操作内容
- 对象结构,对象的元素的管理
代码示例
-
创建访问者抽象类
public interface Person { void feed(Cat cat); void feed(Dog dog); }
-
创建具体访问者
public class Someone implements Person { @Override public void feed(Cat cat) { System.out.println("其他人喂食猫"); } @Override public void feed(Dog dog) { System.out.println("其他人喂食狗"); } } public class Owner implements Person { @Override public void feed(Cat cat) { System.out.println("主人喂食猫"); } @Override public void feed(Dog dog) { System.out.println("主人喂食狗"); } }
-
创建抽象元素
public interface Animal { void accept(Person person); }
-
创建具体元素
public class Cat implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("好好吃,喵喵喵!!!"); } } public class Dog implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("好好吃,汪汪汪!!!"); } }
-
创建对象结构
public class Home { private List<Animal> nodeList = new ArrayList<Animal>(); public void action(Person person) { for (Animal node : nodeList) { node.accept(person); } } //添加操作 public void add(Animal animal) { nodeList.add(animal); } }
-
创建测试类
public void test21(){ Home home = new Home(); home.add(new Dog()); home.add(new Cat()); Owner owner = new Owner(); home.action(owner); Someone someone = new Someone(); home.action(someone); }
10. 中介者模式
中介者模式是用来对象之间解耦合的操作,对象和对象之间的关系由原来的多对多的关系改成一对多的关系,此模式也叫调停模式
优点:对象之间解耦合,将网状对象关系进行化解开来,方便扩展,减少子类的生成
缺点:如果同事对象比较多的话,中介者会变得很庞大,造成难以维护
主要由四个角色
- 抽象中介
- 具体中介
- 抽象同事类
- 具体同事类
代码示例
-
创建抽象中介接口
public abstract class Mediator { //申明一个联络方法 public abstract void constact(String message,Person person); }
-
创建具体中介类
public class MediatorImpl extends Mediator { private PersonImpl1 p1; private PersonImpl2 p2; public PersonImpl1 getP1() { return p1; } public void setP1(PersonImpl1 p1) { this.p1 = p1; } public PersonImpl2 getP2() { return p2; } public void setP2(PersonImpl2 p2) { this.p2 = p2; } @Override public void constact(String message, Person person) { if (person == p1){ p2.getMessage(message); }else { p1.getMessage(message); } } }
-
创建抽象同事接口
public abstract class Person { protected String name; protected Mediator mediator; public Person(String name,Mediator mediator){ this.name = name; this.mediator = mediator; } }
-
创建具体同事类
public class PersonImpl1 extends Person { public PersonImpl1(String name, Mediator mediator) { super(name, mediator); } //与中介者联系 public void constact(String message){ mediator.constact(message, this); } //获取信息 public void getMessage(String message){ System.out.println("租房者" + name +"获取到的信息:" + message); } } public class PersonImpl2 extends Person { public PersonImpl2(String name, Mediator mediator) { super(name, mediator); } //与中介者联系 public void constact(String message){ mediator.constact(message, this); } //获取信息 public void getMessage(String message){ System.out.println("租房者" + name +"获取到的信息:" + message); } }
-
创建测试类
public void test21(){ MediatorImpl mediatorImpl = new MediatorImpl(); PersonImpl1 p1 = new PersonImpl1("张三", mediatorImpl); PersonImpl2 p2 = new PersonImpl2("李四", mediatorImpl); mediatorImpl.setP1(p1); mediatorImpl.setP2(p2); p1.constact("我要租房"); p2.constact("我有房子"); }
11. 解释器模式
解释器模式,就是定义语言的文法,并建立一个解释器来解释该语言中的句子,通过构建解释器,解决某一频繁发生的特定类型问题实例,举例来说,你的老板说小伙子,最近表现不错,公司对你很认可,继续努力哈,然后说,那谁谁谁表现还可以,上升空间很大,也继续努力,虽然看起来都是在夸两个人,但是却隐晦的夸一个,批评一个,要是有个什么东西能够解释这种语言就可以了,解释器就是干这个事的
这个模式有五个角色
- 解释器接口
- 抽象非终结表达式
- 非终结表达式实现类
- 终结表达式
- 环境类
代码举例
-
创建环境类
public class Context { private Map<Expression, Integer> map = new HashMap<>(); //定义变量 public void add(Expression s, Integer value) { map.put(s, value); } //将变量转换成数字 public int lookup(Expression s){ return map.get(s); } }
-
解释器接口
public interface Expression { int interpreter(Context context);//一定会有解释方法 }
-
创建抽象非终结表达式
public abstract class NonTerminalExpression implements Expression{ Expression e1,e2; public NonTerminalExpression(Expression e1, Expression e2){ this.e1 = e1; this.e2 = e2; } }
-
非终结表达式实现类
public class MinusOperation extends NonTerminalExpression { public MinusOperation(Expression e1, Expression e2) { super(e1, e2); } //将两个表达式相减 @Override public int interpreter(Context context) { return this.e1.interpreter(context) - this.e2.interpreter(context); } } public class PlusOperation extends NonTerminalExpression { public PlusOperation(Expression e1, Expression e2) { super(e1, e2); } //将两个表达式相加 @Override public int interpreter(Context context) { return this.e1.interpreter(context) + this.e2.interpreter(context); } }
-
终结表达式
public class TerminalExpression implements Expression{ String variable; public TerminalExpression(String variable){ this.variable = variable; } //获得该变量的值 @Override public int interpreter(Context context) { return context.lookup(this); } }
-
测试类
public class Test { public static void main(String[] args) { Context context = new Context(); TerminalExpression a = new TerminalExpression("a"); TerminalExpression b = new TerminalExpression("b"); TerminalExpression c = new TerminalExpression("c"); context.add(a, 4); context.add(b, 8); context.add(c, 2); //new PlusOperation(a,b).interpreter(context)--->返回12 // c.interpreter(context)--->2 //MinusOperation(12,2)..interpreter(context)--->10 System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)); } }