23种设计模式-完结!

23种设计模式(Java版本)

总述

​ 在大四的时候开始了作为一个码农的实习阶段,在进入公司之前,我只知道一些简单的工厂模式——比如简单工厂(对应还有“复杂”工厂)、单例模式、代理模式,但是这些也只是大概了解而已,其实并不能很好地描述出来。在实习开始的一个星期中,阅读所在部门正在开发的系统。我发现里面就有自己影响中的设计模式;在小组进行例会的时候,都在讨论怎样优化代码,我越来越意识到设计模式对项目的重要性,所以从今天开始,一天搞定一个设计模式!冲就完事!

​ 设计模式,是程序员前辈们实际开发总结出来的经验,解决特定问题的一种思路,有过一定课程设计的同学都会在自己动手的过程中去思考解决类与类之间,服务端与客户端之间的一种关系怎样解决才是最好的。目的就是为了提高代码可复用性、可维护性、可读性、稳健性。

​ 本文章基于阅读《大话设计模式》来进行编写,所以例子与书中的大部分会相同。

一、简单工厂模式

1. 概述

使用一个工厂类生产对应的工具类,来完成特定的功能,使得实现功能内部的代码对用户透明。

2. UML图

在这里插入图片描述

3. 代码实现

3.1 首先是运算类,然后是它的加减乘除子类,代码如下(省略getter setter):

// 父类
public class Operation {
    
    

    private double a = 0;
    private double b = 0;

    public double getResult(){
    
    
        return 0;
    }

}

// 子类
// 加法
public class OperationAdd extends Operation {
    
    

    private double a;
    private double b;

    public OperationAdd() {
    
    
    }

    @Override
    public double getResult(){
    
    
        return this.a + this.b;
    }
}

// 减法
public class OperationDiv extends Operation {
    
    

    private double a;
    private double b;

    public OperationDiv() {
    
    

    }

    @Override
    public double getResult(){
    
    
        return this.a / this.b;
    }
}

// 乘法
public class OperationMul extends Operation {
    
    

    private double a;
    private double b;


    public OperationMul() {
    
    

    }

    @Override
    public double getResult(){
    
    
        return this.a * this.b;
    }
}

// 除法
public class OperationSub extends Operation {
    
    

    private double a;
    private double b;


    public OperationSub() {
    
    

    }

    @Override
    public double getResult(){
    
    
        return this.a - this.b;
    }
}

3.2 其次是工厂类(创建工厂的方法一般设置为静态方法,方便调用),用来创建对应的运算实现类

// 运算工厂
public class OperationFactory {
    
    

    private static Operation operation;

    public static Operation creatOperation(String symbol){
    
    
        switch (symbol){
    
    
            case "+":{
    
    
                operation = new OperationAdd();
                break;
            }
            case "-":{
    
    
                operation = new OperationSub();
                break;
            }
            case "*":{
    
    
                operation = new OperationMul();
                break;
            }
            case "/":{
    
    
                operation = new OperationDiv();
                break;
            }
            default:{
    
    
                System.out.println("参数不合法");
                break;
            }
        }
        return operation;
    }
}

3.3 最后是使用主函数调用:

public class client {
    
    

    public static void main(String[] args) {
    
    
        Operation operation = OperationFactory.creatOperation("/");
        operation.setA(5);
        operation.setB(2);
        System.out.println(operation.getResult());

    }
}

4. 总结与思考

通过动手实现,我们发现,判断的代码放在了工厂类里面,并紧接着创建了对应的功能实现类,实现了我们功能对用户的透明。但是整个过程你会发现,我们仍然有多余的代码。比如我们要执行多个运算的时候,总是要调用setter方法来对属性赋值,这时候我们通常会思考使用构造器的方式来赋值,进一步的,我们会想到直接在工厂中设置这样的属性。总而言之,在动手任何代码的时候,我们的想法始终都会围绕着“代码美观”和减少我们的代码量来思考,这便是“设计模式”诞生的原因吧,毕竟如果实现同一个功能而能用最少的代码,是我这种码农的渴望的。

二、策略模式

1. 概述

通过上面简单工厂设计模式的动手练习,阅读代码的时候我们同样会发现一个繁琐的事情——每当我们通过工厂来创建对应的功能实现类的时候,都要在客户端新建出来一个功能实现类,然后再使用这个类的方法来计算客户端需要的结果。那么,有没有一种设计模式,让我们的客户端只使用一个类就能直接返回计算的结果呢?这种设计模式就是策略模式!

2. UML图

在这里插入图片描述

我们可以发现,它的uml图和工厂模式没有区别,但是使用体验上确实完全不同的,具体的差别我们看下面的代码实现板块

3.代码实现

3.1 首先还是算法实现类,这一块与简单工厂模式没有区别,只是我们的类名发生的变化:

// 抽象算法类
public abstract class Strategy {
    
    

    private double a = 0;
    private double b = 0;

    public double getResult(double a, double b){
    
    
        return 0;
    }

}

// 子类加法
public class StrategyAdd extends Strategy {
    
    

    @Override
    public double getResult(double a, double b) {
    
    
        return a + b;
    }
}

// 子类减法
public class StrategySub extends Strategy {
    
    

    @Override
    public double getResult(double a, double b) {
    
    
        return a - b;
    }
}

// 子类乘法
public class StrategyMul extends Strategy {
    
    

    @Override
    public double getResult(double a, double b) {
    
    
        return a * b;
    }
}

// 子类除法
public class StrategyDiv extends Strategy {
    
    

    @Override
    public double getResult(double a, double b) {
    
    
        return a / b;
    }
}

可以看到,这种算法类的设计是与简单工厂没有什么区别的(因为这里只有a, b两个属性,所以子类没有定义而默认用于父类的两个属性,getter,setter已经省略),关键在于对应的context类与工厂类发生了明显的变化:

// 策略模式上下文
public class Context {
    
    

    Strategy strategy;

    public Context(String symbol){
    
    
        switch (symbol){
    
    
            case "+":{
    
    
                this.strategy = new StrategyAdd();
                break;
            }
            case "-":{
    
    
                this.strategy = new StrategySub();
                break;
            }
            case "*":{
    
    
                strategy = new StrategyMul();
                break;
            }
            case "/":{
    
    
                strategy = new StrategyDiv();
                break;
            }
            default:{
    
    
                System.out.println("参数不合法");
                break;
            }
        }
    }

    public double getResult(double a, double b){
    
    
        return strategy.getResult(a, b);
    }

}
// 客户端实现代码
public class Client {
    
    

    public static void main(String[] args) {
    
    
        Context context = new Context("+");
        double result = context.getResult(5, 6);
        System.out.println(result);
    }
}

他们之间的区别就出来了——策略模式将选择判断创建功能的函数放在了context里面,并初始化了自己的策略属性,然后定义一个getResult方法,通过两次参数的传递,里面实际使用的策略具体实现类的方法。这样一来,在我们的客户端,只需要简单的传递两个值,不用关心到底是初数化那个功能实现类(策略),便也可以直接得到自己想要的结果,这就是策略模式的好处。

4.总结与思考

我相信绝大部分的人在动手了上述的两个设计模式的时候,都可以清晰地说出来设计模式的诞生过程,并且会在以后的项目中思考怎样才能写出更优越的代码。当然,写完上面的代码你或许会思考,每次我要添加一个算法的时候,都要去修改context里面的switch语句,当然创建对应的策略实现类时必须的。但是怎样可以不用修改switch语句呢?是有办法的,那边是反射!代码如下:

// 新建一个类用来将symbol与对应的策略类型类映射
public class StrategyRef {
    
    

    public static HashMap<String,Class> setRefStra = new HashMap<>();

    static {
    
    
        setRefStra.put("+", StrategyAdd.class);
        setRefStra.put("-", StrategySub.class);
        setRefStra.put("*", StrategyMul.class);
        setRefStra.put("/", StrategyDiv.class);
    }
}

// 将context 中的switch语句用反射的方法替换
public class Context {
    
    

    Strategy strategy;

    public Context(String symbol) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    
        strategy = (Strategy) StrategyRef.setRefStra.get(symbol).newInstance();
    }

    public double getResult(double a, double b){
    
    
        return strategy.getResult(a, b);
    }

}

这样的话代码是不是精简了许多呢?更重要的是在我们新添加一个策略的时候,不用去修改context中的代码,只需继承strategy即可,这便是反射的好处!

三、装饰模式

1.概述

装饰模式正如它的名字那样可以动态地添加一些装饰,这个过程就像我们穿衣服那样,我们穿衣服可以选择穿衣服的顺序,不同衣服之间的组合以及衣服的数量。下面我们以穿衣服为例去探讨这个装饰模式。

2.UML图

在这里插入图片描述

3.代码实现

//  定义一个最原始的抽象类(接口也可以),所有的对象都对这个类展开进行扩展
public abstract class Component {
    
    
    public abstract void show();
}
// person继承上面的类,并有自己的属性。代表穿衣服的人
public class Person extends Component {
    
    

    private String name;

    public Person() {
    
    
    }

    public Person(String name) {
    
    
        this.name = name;
    }

    public void show(){
    
    
        System.out.println(this.name + "的装扮");
    }
}
// 服饰类
public abstract class Finery extends Component{
    
    

    private Component component; // 装饰类的重点属性

    public Finery(Component component) {
    
    
        this.component = component;
    }

    public void show(){
    
    
        component.show();
    }
}

// 服饰类T恤子类
public class TShirts extends Finery {
    
    

    public TShirts(Component component) {
    
    
        super(component);	// 调用父类的构造器,为父类初始化了component
    }

    @Override
    public void show() {
    
    
        super.show();
        System.out.println("T裇");
    }
}

// 服饰类裤子子类
public class Trouser extends Finery {
    
    

    public Trouser(Component component) {
    
    
        super(component);
    }

    @Override
    public void show() {
    
    
        super.show();
        System.out.println("裤子");
    }
}

// 服饰类帽子子类
public class Hat extends Finery {
    
    

    public Hat(Component component) {
    
    
        super(component);
    }

    @Override
    public void show() {
    
    
        super.show();
        System.out.println("戴帽子");
    }
}
public class Client {
    
    

    public static void main(String[] args) {
    
    
        // 通过不断地往各自父类对象的Component初始化值,达到装饰模式的要求
        Component person = new Person("achao");
        person = new TShirts(person);
        person = new Trouser(person);
        person = new Hat(person);
        person.show();
    }
}

运行结果:
在这里插入图片描述

4.总结与思考

装饰模式的出现,使得我们对一个类扩展它的功能的时候不用去它里面添加相应的方法,而是通过上面的uml图中的关系,只要添加一个新的类,并向里面传递参数即可。这样做的好处就是不会破坏原有的代码,坏处就是多层嵌套的时候比较复杂,不利于检查错误。特别是最后发现最里面的层出现了错误的时候,整个人会崩溃。

四、代理模式

1.概述

​ 代理,这个词相信大家都不陌生,在我们的生活中,朋友圈都会看到有朋友在微信上做“微商”,但实际上他们并不是自己全权处理商品的流通过程,他们也只是找一个真正做微商的卖家拿货,然后作为他们的一个中介,赚着中介费,因为一般大学生的生活圈中有消费力的人群比较多,所以那些卖家都是很乐意地去找大学生做代理。

​ 我们回顾一下大学生做代理的一个过程,首先我们的角色有三个——一是卖家,二是大学生代理,三是买家。而这个商品交易的过程大概可以这样描述:首先大学生代理通过向卖家获得商品照片进而以发朋友圈的方式将产品的样式图展示给买家,买家看到这些照片后,进行选择一个自己喜欢的样式,然后找到大学生代理询问相关信息。而大学生代理的信息都完全是从卖家那里拿的,买家觉得合适之后付款,大学生代理再向卖家提供买家订购的商品信息由买家发货。所以这个流程中还有重要的一点就是,卖家不认识买家,不与其直接沟通!

2.UML图

在这里插入图片描述

3.代码实现

// 售卖接口
public interface SellInterface {
    
    
}

// 具体卖家实现售卖接口
public class Seller implements SellInterface{
    
    


    public Seller() {
    
    
    }

    public void sell(){
    
    
        System.out.println("收到的订货信息,开始发货");
    }

}

// 代理类实现售卖接口
// 大学生代理
public class Agent implements SellInterface{
    
    

    private Seller seller;	// 要代理的对象

    public Agent(Seller seller) {
    
    
        this.seller = seller;
    }

    public Agent() {
    
    
    }
    public void sell(){
    
    
        System.out.println("提供商品信息,接受订单");
        this.seller.sell();
        System.out.println("交易完成");
    }
}

// 客户端实现(相当于买家)
public class Client{
    
    

    public static void main(String[] args) {
    
    
        new Agent(new Seller()).sell();
    }

}

运行结果:
在这里插入图片描述

4.总结与思考

代理模式的核心在与真实对象和代理对象都继承了同一个接口,然后在代理对象中初始化一个真实对象,外部使用这个代理类的时候使用的方法代理类便直接调用真实对象的方法,期间,代理类可以对两者的信息做一定的处理,有自己的处理模式,实现两者之间的隔离,保护真实对象不被攻击等。

五、工厂模式

1、概述

学习了简单工厂模式,大概都对工厂有了一定的了解,简单来说,工厂就是为了“生产出”各种功能类而创建的。但是在简单工厂这种设计模式下我们经常会面临一个问题:当我们需要新添加一个数学方法的时候,我们不仅要添加一个算法实现类,还要修改工厂里面原有的代码去创建这个类,这与我们设计模式的初衷就产生了冲突。于是我们出现了从简单工厂转换成为了工厂模式。

2.UML图

在这里插入图片描述

由上面的uml图可以看出,工厂模式与简单工厂模式最大的不同在于工厂模式对应每种"产品"都有 对应的工厂类来实现,而简单工厂模式都是由一个工厂来负责生产所有的产品的,可见如果这个工厂“出现”了问题。那势必影响到其他产品的生产。

3、代码实现

4、总结与思考

六、单例模式

1、概述

单例模式,可能说诸多入门者了解或者听过最多的一种设计模式了。它作为一种设计模式的初衷就是为了解决我们在使用一些常用的类的时候频繁的创建一个实例,比如我们上面讲到的工厂模式,我们要获得对应的工厂的时候经常要实例化一个工厂,这就很浪费我们的内存。单例模式的出现就是避免我们在实例化一个工厂的情况下再次创建一个这个对象,保证我们对对象的重复使用,使得代码运行高效。

简单来说,单例模式分为两种:懒汉模式和饿汉模式。所谓的懒汉模式就是在没有使用的情况下,目标对象不会被实例化出来,当真正使用的时候才会创建这个对象,相对的饿汉模式,一开始初始化该类的时候对象就已经创建了(使用静态修饰),这涉及到类的加载机制,后面的例子会详细的说明。

2、懒汉模式

2.1静态内部类实现单例模式:

// 静态内部类实现单例
class static Singletest1{
    
    
	// 私有构造器
	private Singletest1(){
    
    
		
	}
	
	// 静态内部类 
	private static class innerClass{
    
    
		private static Singletest1 singletest1 = new Singletest1();
	}
	
	// 获得实例的公开方法
	public static Singletest1 getInstance(){
    
    
		return innerClass.singletest1;
	}
}

说明:

类(Singletest1)的加载过程中,静态内部类不会被加载,不同于这个类中的静态变量和静态代码块会在链接的时候就开始初始化执行,当使用了Singletest1的静态方法getInstance()的时候,相当于初始化了静态内部类,这个时候内部类的静态变量才开始初始化生成对Singletest1对象的引用。特别需要说明的是,不管你实例化了多少个singletest1对象,拿到的都会是同一个对象,这就保证了我们单例模式的定义。

2.2 静态变量实现单例:

// 静态常量实现单例
class Singletest2{
    
    
	// 私有构造器
	private Singletest2(){
    
    
		
	}
	
	// 私有静态变量Singletest2对象
	private static Singletest2 singletest2 = null;
	
	// 公开获得实例方法
	public static Singletest2 getInstance(){
    
    
		if (singletest2 == null){
    
    		// 判断是否创建了Singletest2对象
			singletest2 = new Singletest2();
		}
		return singletest2;
	}
}

说明:

类(Singletest2)在加载链接的时候就会对静态变量singletest2开始初始化(值为null,几乎不占用内存),当调用getInstance()方法的时候才生成Singletest2的实例。利用了静态常量的特性——在jvm中创建了之后就不能再更改。

问题思考:

上面的静态变量的方式实现的单例模式的实现虽然保证了我们拿到个每个目标对象都是同一个对象(单例的),但是不能保证多线程的状况在对这个对象初始化的时候进行了同时初始化,所以我们必须保证初始化的时候只有一个线程在操作。这就涉及到了我们的加锁操作。

2.3 多线程下的单例模式:

public class Single_Multithreading {
    
    
    // 私有构造方法
    private Single_Multithreading(){
    
    

    }
    private static Single_Multithreading single = null;

    public synchronized static Single_Multithreading getInstance(){
    
    
        // 如果是null,进行初始化;否则直接返回single
        if (single != null){
    
    
            single = new Single_Multithreading();
        }
        return single;
    }
}

使用上面的方法,对getInstance()方法加上锁的时候,解决了多线程访问的问题,但是这个时候我们的性能真的最好的吗?我们可以发现,当我们的single已经初始化的时候,每次有线程要拿到这个single的时候,都要访问这个方法并加上锁,而很多的线程也因为这样等待着这个线程释放锁,白白浪费了时间。当我们的single初始化后,应当每个线程都可以直接拿到这个对象而不是无谓的等待。所以就出现了下面的双重检测锁

双重校检锁

public class Single_Multithreading {
    
    
    // 私有构造方法
    private Single_Multithreading(){
    
    

    }
    private static Single_Multithreading single = null;

    public static Single_Multithreading getInstance(){
    
    
        // 如果是null,进行初始化;否则直接返回single
        if (single == null){
    
    
            synchronized (Single_Multithreading.class){
    
    
                // 再次判断防止其它线程完成初始化
                if (single == null){
    
    
                    single = new Single_Multithreading();
                }
            }
        }
        return single;
    }
}

这样就解决了上面的问题,第二次的检测是为了防止在第一次判断之后有线程实现进入了这个方法(因为之前是没有加锁的)初始化了single。

3、饿汉模式

3.1 静态常量实现单例:

// 静态常量实现单例
class Singletest3{
    
    
	// 私有构造器
	private Singletest3() {
    
    
		
	}
	
	//	静态常量
	private final static Singletest3 SINGLETEST3= new Singletest3();
	
	// 公开方法
	public static Singletest3 getInstance(){
    
    
		return SINGLETEST3;
	}
}

说明:

类(Singletest3)在加载的过程中会在链接的准备阶段就会对静态常量Singletest3进行初始化成Singletest3的一个引用且这个值不能再改变。

3.2 静态代码块实现单例:

// 静态代码块实现单例
class Singletest4{
    
    
    
	private Singletest4(){
    
    
	}
    
	private final static Singletest4 singletest;
    
	static {
    
    
		singletest4 = new Singletest4();
	}
    
	public static Singletest4 getInstance(){
    
    
		return singletest4;
	}
}

说明:

和上述的过程一样,不同的是对singletest4初始化放在了静态代码块中。

4、总结与思考

饿汉模式和懒汉模式的区别:

  • 从上面的代码可以看到,懒汉模式是在调用方法的时候才将目标对象初始化,而饿汉模式在类初始化的时候就已经创建了这个对象。
  • 懒汉模式因为是延迟加载所以要保证多线程访问的情况
  • 饿汉模式不用考虑多线程,但是会一开始没有使用的时候就增加系统的负担

七、原型模式

1、概述

原型模式是将一种类作为原型,对其进行复制,将里面的属性拷贝出来成为一个新的对象。适用于某个经常要使用的类但是它初始化的时候要消耗很多的资源,这时候我们可以使用原型模式。因为拷贝只是直接在内存中复制一份,速度很快。

2、UML图

在这里插入图片描述

3、代码实现

定义一个原型抽象类:

// 要实现拷贝重要的是实现cloneable接口
// 我这里直接定义了一些公共的属性
public abstract class Prototype implements Cloneable {
    
    
    // 科目
    private String course;
    // 授课老师
    private String teacher;
    // 学生成绩
    private Map<String, String> scores;

    public Prototype() {
    
    
    }

    public Prototype(String course, String teacher, Map<String, String> scores) {
    
    
        this.course = course;
        this.teacher = teacher;
        this.scores = scores;
    }

    // 抽象的克隆方法
    public Prototype clone() throws CloneNotSupportedException {
    
    
        return (Prototype) super.clone();
    }

原型继承类

// 因为属性都和原型抽象类一样,所以只要使用父类的构造器和父类的clone方法即可
public class MathScore extends Prototype {
    
    

    public MathScore(String course, String teacher, Map scores){
    
    
        super(course, teacher, scores);
    }
    @Override
    public Prototype clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }
}
// 还有一个类同理,这里就不列出来了

客户端实现代码

public class Client {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Map<String, String> scores = new HashMap<>();
        scores.put("achao", "99");
        scores.put("jh", "89");
        scores.put("lz", "79");
        // 初始化一个mathscore对象
        MathScore mathScore = new MathScore("math", "Mr.zhang", scores);
        // 调用克隆方法 将mathscore中的属性拷贝给math
        MathScore math = (MathScore) mathScore.clone();
        // 输出属性
        System.out.println(math.toString());
    }
}
// 运行结果:
// Prototype{course='math', teacher='Mr.zhang', scores={achao=99, jh=89, lz=79}}

需要说明的是,这里实现的仅仅是“浅复制”,对基本地属性值可以进行完全的复制(String虽然是引用数据类型,但是也是可以完全复制的,互不影响),但是对于复杂的类型,比如数组、集合、引用对象不可以复制,而是一种“共用”,某一个原型复制出来的对象对该复杂属性进行更改时,对应的原型对象的属性也被更改——即对复杂引用的拷贝只是一种引用拷贝。 深复制与浅复制将在另一个笔记中记录。

4、总结与思考

参考网上的资料与自身的测试,原型模式对访问权限受限的类也可以直接地复制,因为克隆不用访问类的构造器,在我们使用的时候特别地注意是否要将复杂类型复制也就是考虑要不要深复制。原型模式的灵活使用还得靠我们自身对这个模式的理解程度。

八、模板方法模式

1、概述

这个设计模式我们在实际地工作中用得比较多,因为面向对象的特点就有多态,所以在使用多态的时候把不同的方法按照一定的步骤组合在一起使用,这就是模板设计模式的应用场景。在我实习过程中发现,往往在服务层有不同的service之间有继承的关系,在调用几个方法时,使用的一般都是子类自己覆盖的方法去实现对应的功能的。

2、UML图

在这里插入图片描述

3、代码实现

// 父类文件夹服务
public abstract class FolderService {
    
    
    public abstract void searchFolder();
    public abstract void searchDocument();
    public abstract void searchAttributes();
    // 主要做的工作,将上面的方法组合起来
    public void job(){
    
    
        searchFolder();
        searchDocument();
        searchAttributes();
    }
}

// 子类实现
public class DocFolderService extends FolderService {
    
    
    @Override
    public void searchFolder() {
    
    
        System.out.println("正在查找DocFolder。。。");
    }

    @Override
    public void searchDocument() {
    
    
        System.out.println("正在DocFolder中查找Doc。。。");
    }

    @Override
    public void searchAttributes() {
    
    
        System.out.println("正在Doc中查找属性。。。");
    }
}

// 子类实现
public class PdfFolderService extends FolderService {
    
    
    @Override
    public void searchFolder() {
    
    
        System.out.println("正在查找PdfFolder。。。");
    }

    @Override
    public void searchDocument() {
    
    
        System.out.println("正在PdfFolder中查找pdf。。。");
    }

    @Override
    public void searchAttributes() {
    
    
        System.out.println("正在pdf中查找属性。。。");
    }
}

// 客户端代码
public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型的作用大家都知道
        FolderService service;
        service = new PdfFolderService();
        service.job();
    }
}

// ********************运行结果*********************
	/*
        正在查找PdfFolder。。。
        正在PdfFolder中查找pdf。。。
        正在pdf中查找属性。。。*/

4、总结与思考

这个设计模式算是最简单的一个了吧,基本掌握了面向对象的都很熟悉这样的写法。值得一提的是,上面的代码中父类的三个方法定义的时候都是抽象的,供子类实现,这不是固定的哦!学习设计模式主要是学习思想,具体使用的过程中父类的方法看情况而定,不是抽象的话可能是子类需要和父类共用这个方法——至少实习过程中碰到的就是这样的意思!

九、外观模式

1、概述

外观模式,又称为门面模式。指的是客户端使用集体服务的时候不用关心底层的实现,只要使用这个类的方法就能获得自己想要的数据,具体底层的实现让这个类去打交道。典型的使用就是MVC三层架构模式中的dao、service、controller层。有使用过springmvc的都有这个模式的基础概念。

需要注意的是,这个模式和代理模式很是相似,可以说对于客户端来说的效果是一样的,区别就是代理模式中的代理类和被代理者都要实现共同的接口,拥有共同的方法,而且代理的对象可以灵活改变。

2、UML图

在这里插入图片描述

3、代码实现

// dao层实现
// 数据访问层1
public class DaoOne {
    
    
    public void searchPdf(){
    
    
        System.out.println("查找到了文件夹A下的pdf");
    }
}

// 数据访问层2
public class DaoTwo {
    
    
    public void searchWord(){
    
    
        System.out.println("查找到了文件夹A下的word");
}
}

// 数据访问层3
public class DaoThree {
    
    
    public void searchExcel(){
    
    
        System.out.println("查找到了文件夹A下的excel");
    }
}

// service层实现
// 数据服务层
public class Service {
    
    
    private DaoOne one;
    private DaoTwo two;
    private DaoThree three;

    // 默认初始化dao属性
    public Service() {
    
    
        this.one = new DaoOne();
        this.two = new DaoTwo();
        this.three = new DaoThree();
    }

    public void searchDocument(){
    
    
        // 调用dao层的方法
        one.searchPdf();
        two.searchWord();
        three.searchExcel();
    }
}

// controller层实现(客户端)
public class Controller {
    
    
    public static void main(String[] args) {
    
    
        Service service = new Service();
        service.searchDocument();
    }
}

//******************运行结果***********************
/*
    查找到了文件夹A下的pdf
    查找到了文件夹A下的word
    查找到了文件夹A下的excel
*/

4、总结与思考

实际开发中,这种设计模式可以说是应用的最多的了,充分地展示了面向对象的特点——易复用、易维护、易扩展。将三个类的耦合度降低。其次要注意模式的灵活运用,模式是死的,但是人是活的

十、建造者模式

1、概述

在以前的工厂模式中,使得用户在调用工厂的时候不用关心具体的产品是怎样实现的,用户只需要把“想要的”产品丢给工厂后既可以得到想要的产品类。然后再使用这个产品类的具体方法达到用户想要的操作。而建造者模式类似于上述的过程,不同的是,建造者生产的产品倾向于其内部的“零件”,而不是里面的方法,

2、UML图

在这里插入图片描述

3、代码实现

// 汽车产品
public class CarProduct {
    
    
    // 汽车三大件
    private List<String> parts = new ArrayList<>();


    public List<String> getParts() {
    
    
        return parts;
    }

    public void setParts(List<String> parts) {
    
    
        this.parts = parts;
    }
}
// 汽车成产抽象类
public abstract class Builder {
    
    
    // 建造发动机
    public abstract void builderEngine();
    // 建造变速箱
    public abstract void builderGearbox();
    // 建造底盘
    public abstract void builderChassis();
}

public class BenzBuilder extends Builder{
    
    
    // 待建造的产品
    private CarProduct carProduct;

    public BenzBuilder(){
    
    
        this.carProduct = new CarProduct();
    }
    @Override
    public void builderEngine() {
    
    
        carProduct.getParts().add("奔驰发动机");
    }

    @Override
    public void builderGearbox() {
    
    
        carProduct.getParts().add("奔驰变速箱");
    }

    @Override
    public void builderChassis() {
    
    
        carProduct.getParts().add("奔驰底盘");
    }

    public CarProduct getCarProduct() {
    
    
        return carProduct;
    }

    public void setCarProduct(CarProduct carProduct) {
    
    
        this.carProduct = carProduct;
    }
}

// 奔驰建造者
public class BMWBuilder extends Builder{
    
    
    // 待建造的产品
    private CarProduct carProduct;

    public BMWBuilder(){
    
    
        this.carProduct = new CarProduct();
    }

    @Override
    public void builderEngine() {
    
    
        carProduct.getParts().add("宝马发动机");
    }

    @Override
    public void builderGearbox() {
    
    
        carProduct.getParts().add("宝马变速箱");
    }

    @Override
    public void builderChassis() {
    
    
        carProduct.getParts().add("宝马底盘");
    }

    public CarProduct getCarProduct() {
    
    
        return carProduct;
    }

    public void setCarProduct(CarProduct carProduct) {
    
    
        this.carProduct = carProduct;
    }
}
// 指挥者
public class Director {
    
    
    // 建造者对象
    private Builder builder;

    public Director(Builder builder) {
    
    
        this.builder = builder;
    }

    // 零件生产
    public void product(){
    
    
        builder.builderEngine();
        builder.builderChassis();
        builder.builderGearbox();
    }
}

客户端

public class Client {
    
    
    public static void main(String[] args) {
    
    
        BenzBuilder benzBuilder = new BenzBuilder();
        new Director(benzBuilder).product();
        CarProduct carProduct = benzBuilder.getCarProduct();
        System.out.println(carProduct.getParts().toString());
    }
}

//**************执行结果******************
// [奔驰发动机, 奔驰底盘, 奔驰变速箱]

4、总结与思考

在体会建造者模式的思想的时候,还是要多与工厂模式作为对比,首先两者最后返回的都是一个“产品”,但是建造者模式关心得更多地是建造这个产品的过程中不同“零件”的组成过程,而工厂模式则是侧重于这个”产品“的功能,在实际开发的过程中应该牢牢把握这点,进行模式的选择与应用。

十一、观察者模式

1、概述

观察者模式又叫做发布订阅模式,而提到订阅模式,很多人想到的可能就是微信订阅公众号之类的情境。这种情境就是我们观察者需要使用的场景。在微信订阅的模式中,有公众号和用户两种对象,当用户订阅公众号的时候,公众号会更新自己的用户列表,一旦公众号有文章要发布的时候,就会推送消息提醒用户阅览。在这里,公众号是抽象的,用户也是抽象的。

2、UML图

在这里插入图片描述

3、代码实现

公众号类:

// 公众号发布者
public abstract class Publisher {
    
    
    // 用户列表
    List<Subscriber> subscribers;

    public Publisher() {
    
    
        this.subscribers = new ArrayList<>();
    }

    // 添加用户
    public void addSubscribe(Subscriber subscriber){
    
    
        subscribers.add(subscriber);
    }
     // 通知用户查看消息
     public abstract void notifySubcriber();

    public List<Subscriber> getSubscribers() {
    
    
        return subscribers;
    }

    public void setSubscribers(List<Subscriber> subscribers) {
    
    
        this.subscribers = subscribers;
    }
}

// 具体公众号(实现类)
public class SpecificPublisher extends Publisher {
    
    
    // 通知用户查看消息
    public void notifySubcriber(){
    
    
        subscribers.forEach((subscriber)->{
    
    
            subscriber.acceptMsg();
        });
    }
}

订阅者

// 微信号订阅者抽象
public abstract class Subscriber {
    
    
    private String name;
    private String position;
    public Subscriber(String name, String position) {
    
    
        this.name = name;
        this.position = position;
    }

    public abstract void acceptMsg();

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getPosition() {
    
    
        return position;
    }

    public void setPosition(String position) {
    
    
        this.position = position;
    }
}

// 具体订阅者(学生)
public class Student extends Subscriber {
    
    
    public Student(String name, String position) {
    
    
        super(name, position);
    }

    @Override
    public void acceptMsg() {
    
    
        System.out.println(super.getPosition() + super.getName() + "收到有新的公众号消息!");
    }
}
// 订阅具体(上班族)
public class Worker extends Subscriber{
    
    
    public Worker(String name, String position) {
    
    
        super(name, position);
    }

    @Override
    public void acceptMsg() {
    
    
        System.out.println(super.getPosition() + super.getName() + "收到有新的公众号消息!");
    }
}

客户端实现

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 初始化一个公众号
        SpecificPublisher publisher = new SpecificPublisher();
        // 创建两个订阅者
        Student student = new Student("achao", "student");
        Worker worker = new Worker("zhou", "worker");
        // 订阅公众号
        publisher.addSubscribe(student);
        publisher.addSubscribe(worker);
        // 公众号通知用户查看信息
        publisher.notifySubcriber();
    }
}

3、总结与思考

学过redis的发布订阅就能很好的理解观察者模式的应用场景。

十二、状态模式

1、概述

从概念上来说,状态模式就是当一个对象的内在状态改变时允许改变其内在行为,这个对象看起来是改变了其中的类。状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂(也就是分支比较多)的情况,把状态的判断逻辑转移到表示不同状态的一系列类当中,从而达到把复杂的判断逻辑简化的目的。

2、UML图

在这里插入图片描述

3、代码实现

work类

// 工作
public class Work {
    
    
    // 决定什么状态的条件
    private int time;
    // 状态类(默认的时候是从学习语文的状态开始的)
    private State state = new ChineseState();

    public Work(int time) {
    
    
        this.time = time;
    }
    public void work(){
    
    
    this.state.task(this);
    }
}

状态类

// 状态抽象类
public abstract class State {
    
    
    // 当前工作任务
    public abstract void task( Work work);
}


// 学习语文状态
public class ChineseState extends State{
    
    

    @Override
    public void task(Work work) {
    
    
        // 如果当前时间是8点到10点,学习语文
        if (8 <= work.getTime() && work.getTime() < 10){
    
    
            System.out.printf("当前时间是 %d, 我正在学习语文", work.getTime());
        }else{
    
    // 自动跳转到学习数学的状态
            work.setState(new MathState());
            work.work();
        }
    }
}

// 学习数学的状态
public class MathState extends State {
    
    
    @Override
    public void task(Work work) {
    
    
        // 时间小于12点大于10点学习数学
        if (work.getTime() <= 12){
    
    
            System.out.printf("当前时间为%d,我正在学习数学", work.getTime());
        }else {
    
     // 默认跳转到学习英语的时间
            work.setState(new EnglishState());
            work.work();
        }
    }
}

// 学习英语的状态
public class EnglishState extends State {
    
    
    @Override
    public void task(Work work) {
    
    
        if (work.getTime() < 15){
    
    
            System.out.printf("当前时间为%d, 我正在学习英语");
        }else{
    
      // 默认跳转到学习数理化的状态
            work.setState(new MacState());
            work.work();
        }
    }
}

// 学习数理化的状态
public class MacState extends State {
    
    
    @Override
    public void task(Work work) {
    
    
        if (work.getTime() <= 17){
    
    
            System.out.printf("当前时间为%d, 我正在学习数理化", work.getTime());
        }else {
    
    
            work.setState(null);
        }
    }
}

调用测试

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Work work = new Work(15);
        work.work();
    }
}

// 输出结果:
// 当前时间为15, 我正在学习数理化

4、总结与思考

可见对于一天有计划的学习安排的话(有顺序),状态模式将原始的通过条件判断的来完成的功能,通过分离类的方式,使得判断的语句分到每个类中去了,这样做的好处就是我们在进行程序的扩展和修改的时候,只需要更改对应类中的代码,利用了面向对象的易扩展,易维护的特点。

十三、适配器模式

1、概述

适配器模式的概念是:将一个类的接口转换成为客户希望的另外一个接口。adapter模式使得原本由于接口不兼容而不能 一起工作的那些类可以一起工作。

其实这种情况在我们日常的编码中很容易遇到,看过我webservice入门博客的就知道,webservice中的接口某些数据结构在xml中传送的时候,因为webservice本身的不支持,比如Map类,通常需要写一个适配器来对这个类进行转换,这样客户端在调用接口的时候,使用的其实是适配器中进行转换了的类。

再比如,数据库中经常会有逻辑删除的操作,实现逻辑删除经常使用的方案就是在数据库表中设置逻辑删除的字段,比如1表示数据存在,0表示数据逻辑删除了(实际上还是存在表中,只是对外不可见而已)。而在我们的实际开发中,删除的接口方法以及参数一般都是固定的,为了实现逻辑删除,就需要把参数重新构造一下,构造好的参数传递给更新表的方法,这样就完成的逻辑删除。其中参数重构的过程就是一个适配器。

2、UML图

在这里插入图片描述

3、代码实现

原始删除接口

// 要转换的接口
public class Delete {
    
    
    public void delete(){
    
    
        System.out.println("删除学生表中学号为1100的学生");
    }
}

新的逻辑删除接口

// 逻辑删除
public class LogicDelete {
    
    

    public void delete(){
    
    
        System.out.println("将学生表中学号为1100的学生的删除标志位改为了0");
    }
}

适配器的实现

public class Adapter {
    
    
    private LogicDelete logicDelete = new LogicDelete();
    public void delete() {
    
    
        logicDelete.delete();
    }
}

接口测试

public class Cilent {
    
    
    public static void main(String[] args) {
    
    
        Delete delete = new Delete();
        Adapter adapter = new Adapter();
        delete.delete();
        adapter.delete();
    }
}
// 运行结果
//    删除学生表中学号为1100的学生
//    学生表中学号为1100的学生的删除标志位改为了0

上述的代码可能有瑕疵的地方,读者应该自己认真体会适配器模式的特点,其实上述的代码中的LogicDelete类可以与Delete关联,在使用Delete中的delete()方法的时候,直接调用LogicDelete中的delete方法即可,实际地开发过程中会涉及到参数的转换。

4、总结与思考

adapter这个单词在学习的过程中很容易碰见,不管是在webservice中还是在逻辑删除中,SpringMVC中好像也是有这个适配器需要自定义的。不管怎么样,只要抓住适配器的概念,再结合实际场景中的应用,相信大家都很容易掌握。

十四、备忘录模式

1、概述

备忘录模式就是在我们平日里的生活中接触的非常多,最多的应用场景就是以前打游戏的时候,因为打boss失败,所以这个时候读取进度条恢复到打boss之前的状态。备忘录模式一般使用三个对象来实现,一个是需要备份的对象,一个是备忘录对象,还有一个是备忘录管理者对象。

2、UML图

在这里插入图片描述

3、代码实现

玩家类

// 需要备份的对象,玩家
public class Player {
    
    
    // 生命值
    private int vit;
    // 攻击值
    private int atk;
    // 防御值
    private int def;

    // 初始化属性值都为100
    public Player() {
    
    
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    // 存储当前状态
    public Memento saveMeTo(){
    
    
        return new Memento(this.vit, this.atk, this.def);
    }

    // 从备忘录中回复数据
    public void setMeTo(Memento meTo) {
    
    
        this.vit = meTo.getVit();
        this.atk = meTo.getAtk();
        this.def = meTo.getDef();
    }
    // 打boss后战力衰减
    public void fight(){
    
    
        System.out.println("打完boss之后战力衰减");
        this.vit = 50;
        this.atk = 50;
        this.def = 50;
        System.out.println(this.toString());
    }

    @Override
    public String toString() {
    
    
        return "Player{" +
                "vit=" + vit +
                ", atk=" + atk +
                ", def=" + def +
                '}';
    }
}

备忘录类

// 备忘录
public class Memento {
    
    
    // 生命值
    private int vit;
    // 攻击值
    private int atk;
    // 防御值
    private int def;

    public Memento(int vit, int atk, int def) {
    
    
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }
}

备忘录管理者

// 备忘录管理者
public class MementoController {
    
    
    private Memento memento;

    public Memento getMemento() {
    
    
        return memento;
    }

    public void setMemento(Memento memento) {
    
    
        this.memento = memento;
    }
}

客户端调用

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Player player = new Player();
        MementoController controller = new MementoController();
        System.out.println(player.toString());
        controller.setMemento(player.saveMeTo());
        player.fight();
        System.out.println("开始恢复到打boss之前的状态");
        player.setMeTo(controller.getMemento());
        System.out.println(player.toString());

    }
}

// 运行结果
/*
Player{vit=100,atk=100,def=100}
打完boss之后战力衰减
Player{vit=50,atk=50,def=50}
开始恢复到打boss之前的状态
Player{vit=100,atk=100,def=100}*/

4、总结与思考

从上面的代码可以看到,备忘录模式中的玩家的状态信息是直接存储到内存上的(一个对象上),这样做的缺点就是当玩家的数据很多的时候,对于内存的开销是很大的,所以备忘录模式也不是越多越好,必要的时候还是通过存储到硬盘上来解决此类问题,比如存储在数据库当中。

十五、组合模式

1、概念

一开始听到组合这个词语的时候自然而然地想到“排列组合”这四个字,实际地了解后,发现本身与排列组合是有一定的关系的。想一下我们最原始的计算排列组合的方式,比如计算1、3、7可以组成多少种不同的三位数的时候,小学经常使用的一种方法就是通过树形图来统计不同三位数的个数,如下图所示。实际上,组合模式就是将对象组合成树型结构以表示“部分-整体”的层次结构。在组合模式中,单个对象和组合对象的使用具有一致性。

在这里插入图片描述

上面这种方式就是实现了一种树形的层级结构,接下来我们使用菜单的方式来实现组合模式。实现的菜单树形结构图如下:

在这里插入图片描述

2、UML图

在这里插入图片描述

3、代码实现

组件抽象类

// 组件抽象类
public abstract class Component {
    
    
    // 组件名称
    private String name;

    public Component(String name) {
    
    
        this.name = name;
    }
	
    // 组件通用的方法
    public abstract void add(Component component);
    public abstract void remove();
    public abstract void display(int i);

    public String getName() {
    
    
        return name;
    }   

    public void setName(String name) {
    
    
        this.name = name;
    }
}

菜单组件

// 菜单组件
public class Menu extends Component{
    
    
    // 存放自己下一层的孩子链表
    public List<Component> children= new ArrayList<>();

    public Menu(String name) {
    
    
        super(name);
    }

    // 增加孩子的方法
    @Override
    public void add(Component component) {
    
    
        children.add(component);
    }

    @Override
    public void remove() {
    
    

    }

    // 展示
    @Override
    public void display(int i) {
    
    
        char[] chars = new char[i];
        for (int j = 0; j < chars.length; j++) {
    
    
            chars[j] = '-';
        }
        System.out.println(new String(chars) + getName());
        i ++;
        for (int j = 0; j < children.size(); j++) {
    
    
            children.get(j).display(i);
        }
    }
}

菜品组件

// 菜品组件
public class Dish extends Component {
    
    
    public Dish(String name) {
    
    
        super(name);
    }

    // 一般来说菜品之后没有下一层的结构了,所以在实际地开发过程中让调用者知道这个信息,需要做一些异常处理或者提示信息
    @Override
    public void add(Component component) {
    
    

    }

    @Override
    public void remove() {
    
    

    }

    // 展示
    @Override
    public void display(int i) {
    
    
        char[] chars = new char[i];
        for (int j = 0; j < chars.length; j++) {
    
    
            chars[j] = '-';
        }
        System.out.println(new String(chars) + super.getName());
    }
}

客户端实现

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 菜单作为一个根节点
        Menu root = new Menu("菜单");
        Menu vegetable = new Menu("蔬菜");
        Menu carrot = new Menu("胡萝卜");
        Menu spinacia = new Menu("菠菜");
        // 蔬菜的下一层结构是胡萝卜和菠菜
        vegetable.add(carrot);
        vegetable.add(spinacia);
        Menu meat = new Menu("肉食");
        // 肉食的下一层结构是排骨和鸡肉
        Menu spareribs = new Menu("排骨");
        Menu chicken = new Menu("鸡肉");
        meat.add(spareribs);
        meat.add(spareribs);
        // 菜单的下一层结构是蔬菜和肉食
        root.add(vegetable);
        root.add(meat);
        // 1只是作为根节点的标记
        root.display(1);
    }
}

// 运行结果
/*        -菜单
        --蔬菜
        ---胡萝卜
        ---菠菜
        --肉食 
        ---排骨
        ---排骨*/

4、总结与思考

从上面的代码可以总结出来,实现组合模式的关键在与组件中拥有一个List来存放它的孩子结点,从而实现了层级关系。在具体调用使用的时候都是在父类的方法中循环调用孩子链表的方法,这样的话就能将层级结构中的关系展示了出来。由于实际地开发过程中我并没有遇到过这种情况,所以可能对这个设计模式理解的并不是很深入,但是,只要知道这种设计模式的基本应用和实现的方式,我觉得以后再实际地开发中也是游刃有余的!

十六、迭代器模式

1、概述

说起迭代器,可能大家都很是熟悉吧,首先想到的就是Java集合中的iterator。具体开发的时候很多人都是使用过的,这里就不再展示集合怎么使用迭代器来实现遍历的了。先说迭代器模式的概念:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

2、UML图

在这里插入图片描述

3、代码实现

迭代器类

// 迭代器接口
public interface Iterator {
    
    
    // 判断集合中是否还有下一个元素
    boolean hasNext();

    // 获取集合中的下一个元素
    String next();
}

// 迭代器具体实现类
// 自定义的迭代器
public class MyIterator implements Iterator {
    
    
    // 内置的一个集合对象
    MyGather gather = new MyGather();
    public int current = 0;		// 	记录还没有遍历的位置

    public MyIterator(MyGather gather) {
    
    
        this.gather = gather;
    }


    @Override
    public boolean hasNext() {
    
    
        if (current > gather.gather.length - 1) {
    
    
            return false;
        }
        return true;
    }

    @Override
    public String next() {
    
    
        return gather.get(current++);
    }
}

集合类

// 集合接口类
public interface Gather {
    
    
    // 集合中添加元素
    boolean add(String s);

    // 通过下标获取元素的位置
    String get(int index);
}

// 集合具体实现类
// 自定义的集合
public class MyGather implements Gather {
    
    
    public String[] gather = new String[5];
    // 当前没有元素的位置
    public int current = 0;

    public MyIterator getIterator() {
    
    
        return new MyIterator(this);
    }

    @Override
    public boolean add(String s) {
    
    
        if (current >= 20) {
    
    
            throw new StringIndexOutOfBoundsException("当前集合不能再添加元素了");
        }
        this.gather[current++] = s;
        return true;
    }

    @Override
    public String get(int index) {
    
    
        if (index > 20) {
    
    
            return null;
        }
        return gather[index];
    }
}

客户端调用:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        MyGather myGather = new MyGather();
        myGather.add(new String("A"));
        myGather.add(new String("B"));
        myGather.add(new String("C"));
        myGather.add(new String("D"));
        myGather.add(new String("E"));
        MyIterator iterator = myGather.getIterator();
        while (iterator.hasNext()) {
    
    
            String next = iterator.next();
            System.out.println(next);
        }
    }
}

// 运行结果
/*
A
B
C
D
E
*/

4、总结与思考

上面的代码简单的展示了一个迭代器是怎么样遍历一个集合对象的,当然实现还是过于简单的,主要还是了解这个模式的思想,如果要变成一个通用的迭代器的话,不免要使用泛型和反射的知识,感兴趣的可以自行去看Java中的ArrayList源码和它的迭代器的实现。

十七、桥接模式

1、概述

桥接模式,主要是找到桥两段的类,概念上来说,是将抽象部分与它的实现部分分离,使它们都可以独立的变化,这里的抽像部分和实现部分就是桥的两端。我们先来看下面的一个UML图:

在这里插入图片描述

在为了实现手机类的过程中,从面向对象的思想来看,我们很容易想到使用上面的继承关系来实现这样的需求,但是,在上面的例子中,其实对于类的扩展是麻烦的。主要体现在下面的情况下:

每个品牌手机中的软件(内置游戏、音乐、视频)的类型和数量都是大不相同的,当我们想要扩展一个手机品牌的时候,因为这个品牌手机的软件数量和类型都不相同,我们必须新创建一个类,再往里面添加对应的功能,但是某些品牌的软件又可能是相同的(如手机中的第三方软件),这个时候,我们就不能完成对代码的复用。所以桥接模式诞生,在上面的例子中,手机品牌就是对应的实现部分,软件则是对应的抽象部分。下面,我们它们分离出来。

2、UML图

在这里插入图片描述

3、代码实现

手机类

// 手机抽象类
public abstract class Phone {
    
    
    // 品牌
    String brand;
    // 型号
    String model;
    // 持有的软件
    Map<String, SoftWare> softWares = new HashMap<>();

    public Phone(String brand, String model) {
    
    
        this.brand = brand;
        this.model = model;
    }

    // 启动指定软件
    public abstract void operate(String softName);

    // 安装软件
    public abstract void setSoftWare(SoftWare softWare);
}

// 品牌M、N手机
public class PhoneM extends Phone {
    
    
    public PhoneM(String brand, String model) {
    
    
        super(brand, model);
    }

    @Override
    public void operate(String softName) {
    
    
        SoftWare softWare = this.softWares.get(softName);
        System.out.print(this.brand + this.model);
        softWare.run();
    }

    @Override
    public void setSoftWare(SoftWare softWare) {
    
    
        this.softWares.put(softWare.getSoftName(), softWare);
    }
}

软件类

// 抽象软件类
public abstract class SoftWare {
    
    
    // 软件名
    String softName;

    public SoftWare(String softName) {
    
    
        this.softName = softName;
    }

    public String getSoftName() {
    
    
        return softName;
    }

    public void setSoftName(String softName) {
    
    
        this.softName = softName;
    }

    // 软件执行内容
    public abstract void run();
}

// 品牌M内置视频软件
public class VideoM extends SoftWare {
    
    
    public VideoM(String softName) {
    
    
        super(softName);
    }

    @Override
    public void run() {
    
    
        System.out.println("正在打开品牌M的视频软件");
    }
}

// 品牌N内置视频软件
public class VideoN extends SoftWare {
    
    
    public VideoN(String softName) {
    
    
        super(softName);
    }

    @Override
    public void run() {
    
    
        System.out.println("正在打开品牌N的视频软件");
    }
}

客户端代码

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 创建品牌M对象,并安装相应软件
        PhoneM phoneM = new PhoneM("品牌M", "mate40");
        phoneM.setSoftWare(new VideoM("视频"));
        phoneM.setSoftWare(new King("王者荣耀"));

        // 创建品牌N对象,并安装相应软件
        PhoneN phoneN = new PhoneN("品牌N", "X");
        phoneN.setSoftWare(new VideoN("视频"));
        phoneN.setSoftWare(new King("王者荣耀"));

        // 各自运行软件
        phoneM.operate("视频");
        phoneM.operate("王者荣耀");
        phoneN.operate("视频");
        phoneN.operate("王者荣耀");
    }
}

// 运行结果

/*    品牌Mmate40正在打开品牌M的视频软件
      品牌Mmate40正在打开王者荣耀
      品牌NX正在打开品牌N的视频软件
      品牌NX正在打开王者荣耀
      */

4、总结与思考

还是桥接模式的那句话,实现抽象部分与实现部分的分离,当我们要扩展的时候只需要添加对应的软件类即可,当我们需要复用的时候也很方便。

十八、命令模式

1、概述

将一个请求封装成为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式主要由下面的几种对象来实现:

  • command(命令类):不同的命令需要不同的子类来实现不同的请求
  • receiver(请求类):不同的请求需要不同的子类来实现,命令真正执行的对象
  • invoke(实施者):不同的命令需要不同的子类来实现,主要将命令封装成为作为一个链表,命令的调用者。充当日志的作用,记录和撤销的功能由它来实现

2、UML图

在这里插入图片描述

3、代码实现

请求类

// 请求抽象类
public abstract class Receiver {
    
    
    // 请求的描述
    String action;

    public Receiver(String action) {
    
    
        this.action = action;
    }

    // 请求的行为
    public abstract void action();
}

// 请求实现类
public class ClearReceiver extends Receiver {
    
    
    public ClearReceiver(String action) {
    
    
        super(action);
    }

    @Override
    public void action() {
    
    
        System.out.println(this.action);
    }
}

命令类

// 命令抽象类
public abstract class Command {
    
    
    // 命令的行为内含行为
    Receiver receiver;

    public Command(Receiver receiver) {
    
    
        this.receiver = receiver;
    }
    // 执行命令
    public abstract void execute();
}

// 命令实现类
public class ClearCommand extends Command {
    
    
    public ClearCommand(Receiver receiver) {
    
    
        super(receiver);
    }

    @Override
    public void execute() {
    
    
        this.receiver.action();
    }
}

执行命令类

// 抽象命令类
public abstract class Invoke {
    
    
    // 内含命令链表
    List<Command> commands = new ArrayList<>();
    // 添加命令
    public abstract void addCommand(Command command);
    // 删除命令
    public abstract void removeCommand(Command command);
    // 执行链表中的命令
    public abstract void work();

}

// 命令实现类
public class ClearInvoke extends Invoke {
    
    
    @Override
    public void addCommand(Command command) {
    
    
        this.commands.add(command);
    }

    @Override
    public void removeCommand(Command command) {
    
    
        this.commands.remove(command);
    }

    @Override
    public void work() {
    
    
        this.commands.forEach(command -> {
    
    
            command.execute();
        });
    }
}

客户端代码

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 创建两个请求对象
        Receiver receiver1 = new ClearReceiver("这是请求1");
        Receiver receiver2 = new ClearReceiver("这是请求2");
        // 创建命令的执行者,并添加两个命令
        Invoke Invoke = new ClearInvoke();
        Invoke.addCommand(new ClearCommand(receiver1));
        Invoke.addCommand(new ClearCommand(receiver2));
        Invoke.work();
    }
}

// 执行结果
/*
    这是请求1
    这是请求2
*/

4、总结与思考

在学习命令模式的时候让我想起了策略模式。于是我翻了一下以前的学习笔记,它们主要有下面的区别:

  • 策略模式是将算法封装成了一个类并供这个类使用,根据不同的情况将客户端的需求返回给正确的数据。
  • 命令模式涉及到三个类,两个类做了一个层级的封装,而且最重要的是他有记录(链表),撤销功能,具体客户端调用的时候,客户端想要的是一种提示或者功能。

十九、责任链模式

1、概述

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连着一条链,并沿着这条链传递给请求,直到有一个对象能处理它为止。生活中常见的例子就是请假流程,比如你要请个一天的假,课程老师就能批准,但是如果要请三天的假,则必须得到班主任的同意,如果要请五天以上的假,则必须通过年级主任的批准。

2、UML图

在这里插入图片描述

3、代码实现

批准人类

// 批准人抽象类
public abstract class Approver {
    
    
    Approver superiro;
    // 处理请假请求
    public abstract void handleLeave(Request request);
}

// 批准人英语老师
public class EnglishTeacher extends Approver {
    
    
    {
    
    
        // 设置上级
        this.superiro = new Headmaster();
    }

    @Override
    public void handleLeave(Request request) {
    
    
        // 英语老师只能处理1天的请假请求
        if (request.getDate() == 1) {
    
    
            System.out.println("英语老师批准了" + request.getPerson() + "请假" + request.getDate() + "天的请求。");
        } else {
    
    
            System.out.println("英语老师不能批准大于一天的请假");
            // 不能处理该请求。让上级处理
            this.superiro.handleLeave(request);
        }
    }
}

// 批准人班主任
public class Headmaster extends Approver {
    
    
    {
    
    
        // 设置上级为年级主任
        this.superiro = new Director();
    }
    @Override
    public void handleLeave(Request request) {
    
    
        if (request.getDate() <= 3){
    
    
            System.out.println("班主任批准了" + request.getPerson() + "请假" + request.getDate() + "天的请求。");
        } else {
    
    
            System.out.println("班主任不能批准大于三天的请假");
            // 不能处理。让上级处理。
            this.superiro.handleLeave(request);
        }
    }
}

// 批准人年级主任
public class Director extends Approver {
    
    
    @Override
    public void handleLeave(Request request) {
    
    
        // 年级主任可以处理任何的请假请求
        System.out.println("年级主任批准了" + request.getPerson() + "请假" + request.getDate() + "天的请求。");
    }
}

请求类

public class Request {
    
    
    // 请假人
    private String person;
    // 请假时长
    private int date;
}

客户端调用

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 客户端不管具体是谁处理的,只需要找一个人就可以了
        EnglishTeacher englishTeacher = new EnglishTeacher();
        englishTeacher.handleLeave(new Request("achao", 1));
        englishTeacher.handleLeave(new Request("datou", 3));
        englishTeacher.handleLeave(new Request("maizai", 30));
    }
}

// 运行结果
/*
        英语老师批准了achao请假1天的请求。
        英语老师不能批准大于一天的请假
        班主任批准了datou请假3天的请求。
        英语老师不能批准大于一天的请假
        班主任不能批准大于三天的请假
        年级主任批准了maizai请假30天的请求。
*/

4、思考与总结

责任链模式很好理解,主要就是将具体的对象抽象成链中的结点,每个结点都有相似的功能,从下级到上级,客户端用管具体是谁处理了自己的请求,只需要固定地需找一个对象就能处理自己提交的请求。当然,实际地请假流程中可能同一个级别需要几个人联合地审批,这种情况下我们需要将责任链模式的结构进行更改,自然而然就想到了结合组合模式。

二十、中介者模式

1、概述

中介者模式又称为调停者模式,好比联合国组织用来 调和多个国家之间的和平与安全,解决国际间经济、社会、文化和人道主义性质的问题。用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其耦合度松散,而且可以独立地改变它们之间的交互。

2、UML图

在这里插入图片描述

3、代码实现

中介者类

// 抽象中介者
public abstract class Mediator {
    
    
    // 默认是两个对象的通信
    public Country china;
    public Country russia;
    // 中介实现俩者之间的通信
    public abstract void signal(String message, Country country);
}

// 具体的中介
public class ClearMediator extends Mediator {
    
    
    @Override
    public void signal(String message, Country country) {
    
    
        if (country.getClass() == China.class){
    
    
            this.russia.getMessage(message);
        }else {
    
    
            this.china.getMessage(message);
        }
    }
}

要联系的对象——国家

// 抽象类
public abstract class Country {
    
    
    // 认识的中介者
    public Mediator mediator;

    // 通过中介者发送信息
    public void sendMessage(String message){
    
    
        this.mediator.signal(message, this);
    }

    // 收到对方的消息
    public abstract void getMessage(String message);
}

// 具体国家:中国
public class China extends Country {
    
    
    @Override
    public void getMessage(String message) {
    
    
        System.out.println("中国收到了俄罗斯的信息:" + message);
    }
}

// 具体国家:俄罗斯
public class Russia extends Country {
    
    
    @Override
    public void getMessage(String message) {
    
    
        System.out.println("俄罗斯收到了中国的信息:" + message);
    }
}

客户端实现

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Mediator mediator = new ClearMediator();
        Country china = new China();
        Country russia = new Russia();
        // 让国家认识自己发送消息的中介
        china.mediator = mediator;
        russia.mediator = mediator;
        // 中介添加自己需要联系的国家
        mediator.china = china;
        mediator.russia = russia;
        china.sendMessage("下个月一起在千岛群岛军事演练");
        russia.sendMessage("美国找我喝茶,一起吗?");
    }
}

// 运行结果
/*
俄罗斯收到了中国的信息:下个月一起在千岛群岛军事演练
中国收到了俄罗斯的信息:美国找我喝茶,一起吗?
*/

4、总结与思考

一开始我看见这个模式的时候我以为是和代理模式一样的,使用过之后才发现他们的不同点主要有以下的几部分:

  • 代理模式中的代理相当于一座桥,联系了两个对象,但是这两个对象相互之间是透明的,它们只需要认识代理就好了。
  • 而中介模式不一样,国家之间本身就是继承同一个抽象,所以它们是彼此认识的,而且发送消息是有指向性的。
  • 中介模式只是单纯地“传达”两个对象之间的信息,而代理模式主要是解决两方面的需求和自己的需求。

二十一、享元模式

1、概述

运用共享技术有效地支持大量细粒度的对象。 在实际的应用中,String常量池,数据库连接池,缓冲池等都是享元模式的应用,所以说享元模式是池技术的重要实现方式

2、UML图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lXTxBKrB-1606663356082)(C:\Users\achao\AppData\Roaming\Typora\typora-user-images\image-20201123215022790.png)]

3、代码实现

享元类

// 抽象类
public abstract class Flyweight {
    
    

    //内部状态
    public String intrinsic;
    //外部状态
    protected final String extrinsic;

    //要求享元角色必须接受外部状态
    public Flyweight(String extrinsic) {
    
    
        this.extrinsic = extrinsic;
    }

    //定义业务操作
    public abstract void operate(int extrinsic);

    public String getIntrinsic() {
    
    
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
    
    
        this.intrinsic = intrinsic;
    }

}

// 可共享的实现类
public class ConcreteFlyweight extends Flyweight {
    
    

    //接受外部状态
    public ConcreteFlyweight(String extrinsic) {
    
    
        super(extrinsic);
    }

    //根据外部状态进行逻辑处理
    @Override
    public void operate(int extrinsic) {
    
    
        System.out.println("具体Flyweight:" + extrinsic);
    }

}

// 不需要共享的实现类
public class UnsharedConcreteFlyweight extends Flyweight {
    
    

    public UnsharedConcreteFlyweight(String extrinsic) {
    
    
        super(extrinsic);
    }

    @Override
    public void operate(int extrinsic) {
    
    
        System.out.println("不共享的具体Flyweight:" + extrinsic);
    }

}

管理享元类的工厂

public class FlyweightFactory {
    
    

    //定义一个池容器
    private static HashMap<String, Flyweight> pool = new HashMap<>();

    //享元工厂
    public static Flyweight getFlyweight(String extrinsic) {
    
    
        Flyweight flyweight = null;

        if(pool.containsKey(extrinsic)) {
    
        //池中有该对象
            flyweight = pool.get(extrinsic);
            System.out.print("已有 " + extrinsic + " 直接从池中取---->");
        } else {
    
    
            //根据外部状态创建享元对象
            flyweight = new ConcreteFlyweight(extrinsic);
            //放入池中
            pool.put(extrinsic, flyweight);
            System.out.print("创建 " + extrinsic + " 并从池中取出---->");
        }

        return flyweight;
    }
}

客户端实现

public class Client {
    
    

    public static void main(String[] args) {
    
    
        int extrinsic = 22;

        Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
        flyweightX.operate(++ extrinsic);

        Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
        flyweightY.operate(++ extrinsic);

        Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
        flyweightZ.operate(++ extrinsic);

        Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
        flyweightReX.operate(++ extrinsic);

        Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
        unsharedFlyweight.operate(++ extrinsic);
    }

}

// 运行结果
/*
        创建 X 并从池中取出---->具体Flyweight:23
        创建 Y 并从池中取出---->具体Flyweight:24
        创建 Z 并从池中取出---->具体Flyweight:25
        已有 X 直接从池中取---->具体Flyweight:26
        不共享的具体Flyweight:27
*/

4、思考与总结

享元模式还有重要的概念就内部状态和外部状态,内部状态就是对象共享出来的信息,存储在享元对象内部而且不会随着环境的改变而改变,外部状态指的是对象得以依赖的一个标记,与内部状态相反,通常是通过一个参数传递过来的。

二十二、解释器模式

1、概述

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。学习过编译原理可能会比较好理解这种模式。所谓的编译原理,就是将一系列定义的文法规则解释为我们平时能都理解的编程语言。在这里偷个懒,直接使用一个简单的例子

2、UML图

在这里插入图片描述

3、代码实现

表达式类

public interface Expression {
    
    
   public boolean interpret(String context);
}

// 终结符表达式
public class TerminalExpression extends AbstractExpression {
    
    

    @Override
    public void Interpret(Context context) {
    
    
        System.out.println("终端解释器");
    }

}

// 非终结符表达式
public class NonterminalExpression extends AbstractExpression {
    
    

    @Override
    public void Interpret(Context context) {
    
    
        System.out.println("非终端解释器");
    }

}

public class AndExpression implements Expression {
    
    
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public AndExpression(Expression expr1, Expression expr2) {
    
     
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {
    
          
      return expr1.interpret(context) && expr2.interpret(context);
   }
}

上下文类

public class Context {
    
    

    private String input;
    private String output;

    public String getInput() {
    
    
        return input;
    }
    public void setInput(String input) {
    
    
        this.input = input;
    }
    public String getOutput() {
    
    
        return output;
    }
    public void setOutput(String output) {
    
    
        this.output = output;
    }

}

客户端代码

public class Client {
    
    

    public static void main(String[] args) {
    
    
        Context context = new Context();
        List<AbstractExpression> list = new ArrayList<>();

        list.add(new TerminalExpression());
        list.add(new NonterminalExpression());
        list.add(new TerminalExpression());
        list.add(new TerminalExpression());

        for (AbstractExpression abstractExpression : list) {
    
    
            abstractExpression.Interpret(context);
        }
    }

}

// 运行结果
/**
终端解释器
非终端解释器
终端解释器
终端解释器

*/

4、思考与总结

这个模式可以说是在所有设计模式中最难的,但是从上面的例子来说,并不难看懂它的各类之间的关系以及各类的作用,难就难在它的主要作用是来对文法解析,我们比较难懂的或者说比较难以领会的就是文法的解析规则以及代码执行。在这里,因为没接触到具体的应用,所以这里就偷个懒,只要掌握大概地应用,我想日后的工作中还是可以灵活使用的,这里就不花太多的时间去琢磨了(比较懒,比较笨)。

二十三、访问者模式

1、概述

表示一个作用与某个对象结构中的个元素的操作。它可以使你在不改变个元素的类的前提下定义作用于这些元素的新操作。

2、UML图

在这里插入图片描述

3、代码实现

需要对外访问的类

public class ObjectStructure {
    
    
    // 男人女人是它的内部结构
    public List<Person> people = new ArrayList<>();

    // 增加方法
    public void addPeople(Person person){
    
    
        this.people.add(person);
    }

    // 提供给访问者的访问的方法
    public void accrpt(Event event){
    
    
        // 访问者访问所有结构
        people.forEach(person -> {
    
    
            person.meetEven(event);
        });
    }
}

男人女人类

// 抽象类
public abstract class Person {
    
    
    // 标记事件
    public Event event;

    // 碰到事件
    public abstract void meetEven(Event event);
}

// 男人实现类
public class Man extends Person {
    
    
    @Override
    public void meetEven(Event event) {
    
    
        event.manReaction(this);	// 这个方法即为关键,称之为双分派
    }
}

// 女人实现类
public class Woman extends Person {
    
    
    @Override
    public void meetEven(Event event) {
    
    
        event.womanReaction(this);
    }
}

访问者类(事件)

// 抽象类
public abstract class Event {
    
    
    // 男人遇到事件反应
    public abstract void manReaction(Man man);
    // 女人遇到事件反应
    public abstract void womanReaction(Woman woman);
}

// 买车事件类
public class BuyCar extends Event {
    
    
    @Override
    public void manReaction(Man man) {
    
    
        System.out.println("男人买车,考虑再三,钟于性价比——>" + man.getClass().getSimpleName());
    }

    @Override
    public void womanReaction(Woman woman) {
    
    
        System.out.println("女人买车,只看牌子——>" + woman.getClass().getSimpleName());
    }
}

// 逛街事件
public class Shopping extends Event {
    
    
    @Override
    public void manReaction(Man man) {
    
    
        System.out.println("男人逛街,只有一个作用,就是帮女人领东西——>" + man.getClass().getSimpleName());
    }

    @Override
    public void womanReaction(Woman woman) {
    
    
        System.out.println("女人逛街,一个月的运动量都在那里了——>" + woman.getClass().getSimpleName());
    }
}

// 挫折事件类
public class Frustration extends Event {
    
    
    @Override
    public void manReaction(Man man) {
    
    
        System.out.println("男人遇到了挫折,抽支烟抖擞便想明白了一切——>" + man.getClass().getSimpleName());
    }

    @Override
    public void womanReaction(Woman woman) {
    
    
        System.out.println("女人遇到了挫折,还需要多多宽慰——>" + woman.getClass().getSimpleName());
    }
}

客户端实现

public class Client {
    
    
    public static void main(String[] args) {
    
    
        ObjectStructure structure = new ObjectStructure();
        structure.addPeople(new Woman());
        structure.addPeople(new Man());
        // 事件访问structure内部结构
        structure.accrpt(new BuyCar());
        structure.accrpt(new Shopping());
        structure.accrpt(new Frustration());
    }
}

// 运行结果
/**
女人买车,只看牌子——>Woman
男人买车,考虑再三,钟于性价比——>Man
女人逛街,一个月的运动量都在那里了——>Woman
男人逛街,只有一个作用,就是帮女人领东西——>Man
女人遇到了挫折,还需要多多宽慰——>Woman
男人遇到了挫折,抽支烟抖擞便想明白了一切——>Man
*/

4、总结与思考

访问者模式具体的使用记住两个要点,一个就是从定义来说,访问者访问一个类的内部结构的但是确不改变各自的结构,第二个是从实现上来说,需要在被访者者中提供访问元素方法的双分派

总结与思考

学完了所有的设计模式,可以说对java这种面向对象的语言更有了一个全新的认识,回想面向对象的几大优点——易扩展,易维护,可复用,灵活好,一开始学习编程的时候可能很难理解面向对象的这几个优点,但是通过对设计模式的学习,我相信很多的人都能用一句话去概括设计模式的好处,我是这样总结的——在保证一定的功能性需求的前提下,充分发挥面向对象的优点,最终写出高内聚,低耦合的对象模型,这便是设计模式。

我在这个设计模式中并没有去谈及很多的原则,比如,单一职责原则,开放-封闭原则,依赖倒置原则,迪法特原则,等,我认为不必去可以记住这些原则,因为这些原则的诞生本身就是让我们在编写代码的时候去考虑发挥面向对象的优点和高内聚,低耦合的目标。在这里感谢《大话设计模式》的作者,确实能让人通俗易懂的学习到知识。

2020年11月29号,我递交了辞职申请,至此我第一个实习生涯结束,历经三个月。辞职的原因是多方面的,一是自己接下来的时间有些许冲突,而是那里看着也不能学习到更多地东西了。总之,这次的实习经历对我来说还是宝贵的。接下来的时间就考自己好好努力吧!宁可柴门摘星宇,不为琼楼掌灯人!

猜你喜欢

转载自blog.csdn.net/weixin_43967401/article/details/109151715
今日推荐