设计模式通关手册

为什么需要设计模式?

作为一个优秀的程序员,所写出的代码必须具有这些特点:

  • 清晰易懂
  • 易于维护
  • 易于扩展
  • 高效简洁

这几项在当今大规模的面向对象编程的程序中,必然离不开设计模式

本文为设计模式知识点的整理复习,如果希望细细的品味设计模式,这里推荐一本书

《Head First设计模式》

如果你觉得贵(本人就买了一本,还是纸质书爽),这本书已经上传在鄙人的服务器供下载
HeadFirst设计模式.pdf

综合起来讲,大致有23种设计模式(其实大部分天天都在身边)

将其分类为:创建型、行为型、结构型、复合模式

以及以函数式编程为底子的响应式(另外附文章)

一、创建型模式

1.单例模式

单例模式的目标是构建一个只存在一个对象的类,即“单例”

特点

  • 将对象保存在类中(static,共用对象)
  • private的构造方法
  • 获取对象的方法

单例模式有多种实现,下面一步步介绍,这很有必要了解

多种实现的原因主要是:效率与线程安全

下面是基础的单例模式的code

public class SingletonBase {
    
    
    private static SingletonBase data=new SingletonBase();
    private SingletonBase(){
    
    
        System.out.println("初始化");
    }
    public SingletonBase getData() {
    
    
        return data;
    }
}

这里利用了类初始化的步骤来保证data成员的线程安全

如果要达到懒加载(需要时但没有初始化再初始化)需要进行下面的优化

同步锁

public class SingletonLazy {
    
    
    private static SingletonLazy data;
    private SingletonLazy(){
    
    
        System.out.println("初始化");
    }
    public synchronized SingletonLazy getData() {
    
    
        if (data == null) {
    
    
            data=new SingletonLazy();
        }
        return data;
    }
}

在获取对象时进行判断,没有初始化就初始化,为了防止多个线程执行这个方法时多次初始化data,使用synchronized保证线程安全

缺点:synchronized过重,同时这个方法只能同时被一个线程使用。

双重校验锁

public class SingletonLazy {
    
    
    private volatile static SingletonLazy data;
    private SingletonLazy(){
    
    
        System.out.println("初始化");
    }
    public SingletonLazy getData() {
    
    
        if (data == null) {
    
    
            synchronized (SingletonBase.class){
    
    
                if(data==null){
    
    
                    data=new SingletonLazy();
                }
            }
        }
        return data;
    }
}

要点:临界区必须为类对象,属性必须使用volatile关键字

单例模式为什么要使用volatile关键字?

https://blog.csdn.net/weixin_44494373/article/details/112061551

单例模式的实现还有很多,例如无锁的方式,即使用cas的方式

2.工厂模式

用于实现逻辑的封装,通过公共的接口提供对象的实例化服务,在添加新的类时,只需要少量的更改。

根据需求,我们可以设计出简单工厂

简单工厂

简单工厂非一个模式,而是一种编程习惯。

public class SimplePizzaFactory {
    
    
    public Pizza orderPizza(String type) {
    
    
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
	public Pizza createPizza(String type) {
    
    
		Pizza pizza = null;
		if (type.equals("cheese")) {
    
    
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
    
    
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
    
    
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
    
    
			pizza = new VeggiePizza();
		}
		return pizza;
	}
}

简单工厂违反了开闭原则:对扩展开放,对修改关闭

同时,简单工厂限制了产生对象的模式,我们可以利用抽象的方法,让子类去实现产生对象的逻辑

工厂方法模式

public abstract class PizzaFactory {
    
    
	public Pizza orderPizza(String type) {
    
    
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
	public abstract Pizza createPizza(String type);
}

若存在多个工厂生产目标对象,但是因为业务的扩展与需求的发展,定制化与地区差异等等,我们可以将工厂抽象出一层接口,规定需要生产什么类型的对象。

在使用工厂时,选择合适的类型进行初始化即可。

抽象工厂模式

提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

public interface PizzaIngredientFactory {
    
    
	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClam();
}

//这是一个实现类
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    
    
	public Dough createDough() {
    
    
		return new ThinCrustDough();
	}
	public Sauce createSauce() {
    
    
		return new MarinaraSauce();
	}
	public Cheese createCheese() {
    
    
		return new ReggianoCheese();
	}
	public Veggies[] createVeggies() {
    
    
		Veggies veggies[] = {
    
     new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
		return veggies;
	}
	public Pepperoni createPepperoni() {
    
    
		return new SlicedPepperoni();
	}
	public Clams createClam() {
    
    
		return new FreshClams();
	}
}

抽象工厂与工厂方法的异同:

  1. 工厂方法“潜伏”在抽象工厂中。
  2. 工厂方法是一个抽象类,子类实现某个方法;抽象工厂为接口,子类实现所有接口。
  3. 工厂方法有模板方法设计模式的意味。

若需要动态扩展支持的产品,怎么办?

一种可能:使用反射

3.建造者模式

将一个 复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

lombok中的builder。

public class IntBuilder {
    
    
    private Integer a;
    private Integer b;
    public Builder builder(){
    
    
        return new Builder();
    }
    public static class Builder{
    
    
        private Integer a;
        private Integer b;
        public Builder a(Integer a){
    
    
            a=a;
            return this;
        }
        public Builder b(Integer b){
    
    
            b=b;
            return this;
        }
    }
}

Product 产品类

通常是实现了模板方法模式,也就是有模板方法和基本方法,例子中的 BenzModel 和 BMWModel 就属于产品类。

Builder 抽象建造者

规范产品的组建,一般是由子类实现。例子中的 CarBuilder 就属于抽象建造者。

ConcreteBuilder 具体建造者

实现抽象类定义的所有方法,并且返回一个组建好的对象。

例子中的 BenzBuilder 和 BMWBuilder 就属于具体建造者。

Director 导演类

负责安排已有模块的顺序,然后告诉 Builder 开始建造

使用场景:

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时, 则可以使用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使 用建造者模式非常合适。

建造者模式与工厂模式的不同:

建造者模式最主要的功能是基本方法的调用顺序安排,这些基本方法已经实现了, 顺序不同产生的对象也不同; 工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。

4.原型模式

用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象。

说白了就是用clone接口

public class PrototypeClass implements Cloneable{
    
    
    //覆写父类 Object 方法
    @Override
    public PrototypeClass clone(){
    
    
        PrototypeClass prototypeClass = null;
        try {
    
    
            prototypeClass = (PrototypeClass)super.clone();
        } catch (CloneNotSupportedException e) {
    
    
            //异常处理
        }
        return prototypeClass;
    }
}

注意,对于使用了组合的对象,clone复制的是引用,而不是递归复制。

二、行为型模式

5.责任链模式

使多个对象都有机会处理请 求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链, 并沿着这条链传递该请求,直到有对象处理它为止。

例子:Spring MVC中的Handler,Shiro框架

实现:构造一个链表,编写方法判题是否匹配当前,否则调用下一个。

public abstract class Handler {
    
    
    private Handler nextHandler;
    //每个处理者都必须对请求做出处理
    public final Response handleMessage(Request request){
    
    
        Response response = null;
        //判断是否是自己的处理级别
        if(this.getHandlerLevel().equals(request.getRequestLevel())){
    
    
            response = this.echo(request);
        }else{
    
     //不属于自己的处理级别
            //判断是否有下一个处理者
            if(this.nextHandler != null){
    
    
                response =
                        this.nextHandler.handleMessage(request);
            }else{
    
    
                //没有适当的处理者,业务自行处理
            }
        }
        return response;
    }
    //设置下一个处理者是谁
    public void setNext(Handler _handler){
    
    
        this.nextHandler = _handler;
    }
    //每个处理者都有一个处理级别
    protected abstract Level getHandlerLevel();
    //每个处理者都必须实现处理任务
    protected abstract Response echo(Request request);
}

需要注意的问题:

  1. 注意不要构成环
  2. 注意限制调用链的长度,避免破坏系统性能

6.命令模式

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求 排队或者记录请求日志,可以提供命令的撤销和恢复功能。

//命令的抽象
public interface Command {
    
    
	public void execute();
}
//一个命令的实现
public class HottubOnCommand implements Command {
    
    
	Hottub hottub;
	public HottubOnCommand(Hottub hottub) {
    
    
		this.hottub = hottub;
	}
	public void execute() {
    
    
		hottub.on();
		hottub.heat();
		hottub.bubblesOn();
	}
}
//命令执行
public class RemoteControl {
    
    
	Command[] onCommands;
	Command[] offCommands;
	public RemoteControl() {
    
    
		onCommands = new Command[7];
		offCommands = new Command[7];
 
		Command noCommand = new NoCommand();
		for (int i = 0; i < 7; i++) {
    
    
			onCommands[i] = noCommand;
			offCommands[i] = noCommand;
		}
	}
	public void setCommand(int slot, Command onCommand, Command offCommand) {
    
    
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
	public void onButtonWasPushed(int slot) {
    
    
		onCommands[slot].execute();
	}
	public void offButtonWasPushed(int slot) {
    
    
		offCommands[slot].execute();
	}
	public String toString() {
    
    
		StringBuffer stringBuff = new StringBuffer();
		stringBuff.append("\n------ Remote Control -------\n");
		for (int i = 0; i < onCommands.length; i++) {
    
    
			stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
				+ "    " + offCommands[i].getClass().getName() + "\n");
		}
		return stringBuff.toString();
	}
}

命令模式

7.解释器模式

定义语法的表示以及该语法的对应解释

例子:正则表达式

解释器模式

  • AbstractExpression——抽象解释器

    具体的解释任务由各个实现类完成,具体的解释器分别由 TerminalExpression 和 Non-terminalExpression 完成。

  • TerminalExpression——终结符表达式

    实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表 达式,但有多个实例,对应不同的终结符。具体到我们例子就是 VarExpression 类,表达式中的每个终结符都在栈中产生了一个 VarExpression 对象。

  • NonterminalExpression——非终结符表达式

    文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分 别对应到 AddExpression 和 SubExpression 两个类。非终结符表达式根据逻辑的 复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。

  • Context——环境角色

    具体到我们的例子中是采用 HashMap 代替。

8.迭代模式

为集合或者数组构建一个迭代器。它提供一种方法访问一个容器对 象中各个元素,而又不需暴露该对象的内部细节。

经典实例:Iterator接口与List

不需要自己实现,Java已经提供。

9.观察者模式

多个用户订阅一个对象,当对象执行某个方法,将广播信息。

image-20210101195002007

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZ4Ucq2f-1609509301720)(https://jirath.cn//PicGoimage-20210101195023207.png)]

主题需要维护一个订阅者列表,在需要的时机将消息发送给目标

Java内置提供了观察者模式的实现:Observable(已经在9版本过时)

10.中介者模式

用一个中介对象封装 一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散, 而且可以独立地改变它们之间的交互。

public abstract class Mediator {
    
    
	//定义同事类
	protected ConcreteColleague1 c1;
	protected ConcreteColleague2 c2;
	//通过 getter/setter 方法把同事类注入进来
	public ConcreteColleague1 getC1() {
    
    
		return c1;
	}
	public void setC1(ConcreteColleague1 c1) {
    
    
		this.c1 = c1;
	}
	public ConcreteColleague2 getC2() {
    
    
		return c2;
	}
	public void setC2(ConcreteColleague2 c2) {
    
    
		this.c2 = c2;
	}
	//中介者模式的业务逻辑
	public abstract void doSomething1();
	public abstract void doSomething2();
}

11.备忘录模式

在不破坏封装性的前提 下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该 对象恢复到原先保存的状态。

设计模式之备忘录模式

12.状态模式

编译原理中,状态机就使用了状态模式

将目标分为多种状态,每个状态可以根据输入跳转下一个状态。

状态设计模式

13.策略模式

定义一组算法,将每个算法都封装起来,并且使它们之间可以 互换。

将某个操作进行封装,用户可以动态的选择一个策略执行。

image-20210101210837902

14.模板方法模式

定义一个操作中的算法的框 架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义 该算法的某些特定步骤。

利用抽象的特性,在某个方法中调用抽象方法,抽象方法的实现交给子类。可以保证一定的规范

例子:排序算法(提供Comparable)接口的实现)

image-20210101211854452

15.空对象模式

为某个类定义一个空对象(自定的空置),不使用null作为空

防止因为null出现的各种情况。

16.访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在**不改变数据结构(不修改原有内容)**的前提下定义作用于这些元素的新的操作。

访问者模式

图片来源:https://refactoringguru.cn/design-patterns/visitor

三、结构型模式

17.适配器模式

适配器模式提供了将某个类适配到新系统的可能

image-20210101212652015

假设系统需要A类型,我们拥有B类型的实现,我们可以构造一个适配器,该适配器拥有B类型的属性,且实现A类型的方法(利用调用B类型),这样即可成功实现适配。

Spring中提供了大量的Adapter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOBtkVNd-1609509301730)(img/image-20210101212957186.png)]

18.代理模式

为其他对象提供一种代理以控制对这个对象的访问。(为预操作、后续操作等提供可能,并非代理执行)

代理某个类的执行,开发、测试中大量使用了这种模式,Spring AOP操作,Java原生的Proxy都提供了代理

下面是Java动态代理的demo,目标是表达自己的语言,利用代理增加其他的语言

public interface ForeignLanguage {
    
    
    void speak(String name);
}
public interface SayChinese {
    
    
    String speakChinese(String name,String word);
}

说中文的实现与说外语(英文)的实现

public class Chinese implements SayChinese {
    
    
    @Override
    public String speakChinese(String name ,String word) {
    
    
        String say="我会说中文不信?:"+word;
        System.out.println(say);
        return say;
    }
}
public class EnglishForeignLanguageImpl implements ForeignLanguage {
    
    
    @Override
    public void speak(String name) {
    
    
        System.out.println("Hello,my name is "+name);
    }
}

代理对象,实现InvocationHandler接口

public class CokeProxyHander implements InvocationHandler {
    
    
    private Object beProxy;
    private ForeignLanguage myForeignLanguage;
    public CokeProxyHander(Object beProxy,ForeignLanguage myForeignLanguage) {
    
    
        this.beProxy = beProxy;
        this.myForeignLanguage=myForeignLanguage;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("我是Coke,不仅会");
        Object returned= method.invoke(beProxy,args);
        System.out.println("********参数内容**********");
        Arrays.stream(args).forEach(s-> System.out.println(s));
        System.out.println("*************************");
        myForeignLanguage.speak((String) args[0]);
        return null;
    }
}
public class ProxyMain {
    
    
    public static void doTest(SayChinese chinese) {
    
    
        chinese.speakChinese("coke", "中国话");
    }

    public static void main(String[] args) {
    
    
        Chinese chinese =new Chinese();
        //构造出一个对象,这个对象是已经被代理的对象,依旧可以当做原对象类型使用
        SayChinese c=(SayChinese) Proxy.newProxyInstance(SayChinese.class.getClassLoader(),new Class[]{
    
    SayChinese.class},new CokeProxyHander(chinese,new EnglishForeignLanguageImpl()));
        doTest(c);
    }
}

image-20210101214343194

19.装饰器模式

动态地给一个对象添加一些额外的职责。就增加功能来 说,装饰模式相比生成子类更为灵活。

对原来的对象进行包装,提供附加的功能(有点像代理)

实例:JavaIO框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IbYplOyf-1609509301733)(https://jirath.cn//PicGoimage-20210101214536240.png)]

20.桥接模式

将抽象与实现解耦,使得二者可以独立的变化。通过在公共接口和实现中使用继承来达到目的。

经典的三层架构中Service就是桥接模式

Bridge UML class diagram.svg

写出两个不同的圆的坐标和半径。

API1.circle at 1:2 7.5
API2.circle at 5:7 27.5
interface DrawingAPI
{
    
    
    public void drawCircle(double x, double y, double radius);
}

/** "ConcreteImplementor" 1/2 */
class DrawingAPI1 implements DrawingAPI
{
    
    
   public void drawCircle(double x, double y, double radius) 
   {
    
    
        System.out.printf("API1.circle at %f:%f radius %f\n", x, y, radius);
   }
}

/** "ConcreteImplementor" 2/2 */
class DrawingAPI2 implements DrawingAPI
{
    
    
   public void drawCircle(double x, double y, double radius) 
   {
    
     
        System.out.printf("API2.circle at %f:%f radius %f\n", x, y, radius);
   }
}

/** "Abstraction" */
interface Shape
{
    
    
   public void draw();                                            // low-level
   public void resizeByPercentage(double pct);     // high-level
}

/** "Refined Abstraction" */
class CircleShape implements Shape
{
    
    
   private double x, y, radius;
   private DrawingAPI drawingAPI;
   public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI)
   {
    
    
       this.x = x;  this.y = y;  this.radius = radius; 
       this.drawingAPI = drawingAPI;
   }

   // low-level i.e. Implementation specific
   public void draw()
   {
    
    
        drawingAPI.drawCircle(x, y, radius);
   }   
   // high-level i.e. Abstraction specific
   public void resizeByPercentage(double pct)
   {
    
    
        radius *= pct;
   }
}

/** "Client" */
class BridgePattern {
    
    
   public static void main(String[] args)
   {
    
    
       Shape[] shapes = new Shape[2];
       shapes[0] = new CircleShape(1, 2, 3, new DrawingAPI1());
       shapes[1] = new CircleShape(5, 7, 11, new DrawingAPI2());

       for (Shape shape : shapes)
       {
    
    
           shape.resizeByPercentage(2.5);
           shape.draw();
       }
   }
}

21.组合模式

把一组对象组合为一个复杂的单一整体

上述的大多数模式都是以此为基础的。

22.外观模式

隐藏子系统的复杂内部结构,只向外部暴露可访问的通用接口。

用户只能访问外观模式向外提供的功能,无法随意使用或重用系统内部的具体功能。

例子:REST风格的服务,接口开发等等等等。

23.享元模式

通过在相似对象之间共享状态来减少内存占用。

  1. poi框架中,Excel表格的样式是需要单独new并被多个对象使用的(并且不能创建过多的style)
  2. Java字符串常量池

猜你喜欢

转载自blog.csdn.net/weixin_44494373/article/details/112072419
今日推荐