前言

设计模式 是软件设计中常见问题的可重用解决方案,它们是在长期实践中总结出来的最佳实践。用好设计模式可以 使代码逻辑清晰,简化、减少重复代码,提高编程效率
别看设计模式有 23 种之多,无外乎是要处理好这几件事情:

  1. 创建对象
  2. 传递对象
  3. 调用方法

针对于这几件事情 玩出花 来了,就总结为设计模式。为此我还编过一首小诗来记住它们:

# 创建型
建原单例抽工厂, # 建造者,原型,单例,工厂,抽象工厂模式
# 结构型
适组过桥享代装, # 适配,(外观),组合,过滤器,桥接,享元,代理,装饰器模式
# 行为型
任命观状空备策, # 责任链,命令,观察者,状态,空对象,备忘录,策略模式
解释迭代中模访。 # 解释器,迭代器,中介者,模板,访问者模式

其实大可不必都记住它们,只要学会几个完形填空,用啥设计模式就呼之欲出了,下面用我的方式来速通它吧!

预告

只要会填下面各种完形填空,你就会用设计模式了,信不信?

类型 形式 备注
传统型 A a = new A();
a.xxx();
通用形式
方法型 A a = new A();
a.__();
同类不同方法
方法参数型 A a = new A();
a.xxx(__);
同方法,不同参数
单一职责型 __a = new__();
a.xxx();
不同类
多态型 A a = new __();
a.xxx();
同抽象,不同实现类
工厂型 A a = A.getBean(__);
a.xxx();
统一用工厂类产生对象
代理型 A a = new A(__);
a.xxx();
同方法,不同代理类
完全型 __ a = __ __(__);
a.__(__);
可以各种组合

传参尽量往基础类型,字符串,枚举,Class 上靠,那么就会很容易引导你通往设计模式的道路。

传统代码

来一段最正常的写法,工作上也是非常常用,设计模式秘密就在这里面:

A a = new A();  // 新建对象
a.xxx();        // 调用对象方法

如果实现新的功能,输出新的结果时,很容易想到的是直接调用不同的方法:

...
A a = new A();
a.play();        // 会输出 play...
a.work();        // 会输出 work...
...

public class A{
  void play(){
    System.out.println("play...");
  }
  void work(){
    System.out.println("work...");
  }
}

如果还有其他功能,比如study(),sleep()…,就在 A 类中继续加方法就是了,例如:

public class A{
    public void play() {
        System.out.println("play...");
    }
    public void work() {
        System.out.println("work...");
    }
    // 有新的功能就加上新的方法
    public void study() {
        System.out.println("study...");
    }
    public void sleep() {
        System.out.println("sleep...");
    }
...
}

方法型

因此出现了第一种完形填空,对应的横线上只要填上不同的方法名就可以执行出不同的结果了:

A a = new A();  // 新建对象
a._________();  // 调用对象方法

在日常工作当中,这个也是最常见的,如此一来,A 类就有可能变的越来越大,方法越来越多,就成屎山了。

方法参数型

通过我敏锐的观察力,上面几个方法很像,方法内部在执行 System.out.println("xxxx...");, 所以第二种完形填空可以变成:

A a = new A();  // 新建对象
a.doing(_____); // 调用对象方法

举例:

...
A a = new A();  // 新建对象
a.doing("play");   // 输出  play...
a.doing("work");   // 输出  work...
a.doing("study");  // 输出  study...
...
public class A{
  public void doing(String type){
    System.out.println(type + "...");
  }
}

看看,一行代码就解决了,这就是抽象的魅力!当然,这只能算是特殊的情况,正常情况没有这么相像和简单的方法,通用的可以这样变:

...
A a = new A();  // 新建对象
a.doing("play");   // 输出  play...
a.doing("work");   // 输出  work...
a.doing("study");  // 输出  study...
...

public class A{
  // 分发方法
  public void doing(String type){
    if("play"==type){
      play();
    }else if("work"==type){
      work();
    }else if("study"==type){
      study();
    }
  }
  public void play(){
    System.out.println("play...");
  }
  public void work(){
    System.out.println("work...");
  }
  ...
}

是吧,传参方法名,在 doing 方法里面统一派发,调用不同的具体方法,这种形式就是传参比较灵活,有统一的入口

这个变形单从结果上看,情况并没有改善,代码量反而多了doing 这个方法,还有一堆的if...else,这种看的就很难受的,再从其他地方变一下:

单一职责型

那么可以多建几个类,每个类都有一个方法,这样单类的代码量不就下来了吗?也就是单一职责,每个类各司其职,都是本领域的专家。

这样,完形填空变成了如下这种,只要换成不同的类型就行了

___ a = new ___();  // 新建对象
a.xxx();            // 调用对象方法

具体例子:

...
Play a = new Play();  // 玩的对象
a.doing();            // 可劲地玩吧

Work b = new Work();  // 工作对象
b.doing();            // 可劲地工作
...

public class Play{
  public void doing(){
    System.out.println("play...");
  }
}

public class Work{
  public void doing(){
    System.out.println("work...");
  }
}

多态型

PlayWork 都有 doing 方法,不如给它们抽象一下,想像它们是同一类型的,比如说 爱好,玩,工作,学习都是我的爱好,这么说没问题吧?

抽象的好处就是实现多态,后面会看到多态传参的魅力的,设计模式可喜欢它了,完形填空变成这样了

A a = new ____();  // 新建对象
a.xxx();           // 调用对象方法

实现它也不难,多态的可以用 继承(extends)实现(implements) 方式

继承

让它们都继承一个抽象类 A,也就是说都是 A 类型

...
A a = new Play();  // 玩的对象
a.doing();         // 可劲地玩吧

A b = new Work();  // 工作对象
b.doing();         // 可劲地工作
...

// 抽象方法类
public abstract class A{
  protected abstract void doing();    // 抽象方法
}

public class Play extends A{
    @Override
    protected void doing() {
        System.out.println("Play#doing: play...");
    }
}

public class Work extends A{
    @Override
    protected void doing() {
        System.out.println("Work#doing: work...");
    }
}

接口

让它们都实现一个接口类 IA,也就是说都是 IA 类型

...
IA a = new Play();  // 玩的对象
a.call();           // 可劲地玩吧

IA b = new Work();  // 工作对象
b.call();           // 可劲地工作
...
// 接口
public interface IA{
 void call();
}

public class Play implements IA{
    @Override
    public void call() {
        System.out.println("Play#call: play...");
    }
}

public class Work implements IA{
    @Override
    public void call() {
        System.out.println("Work#call: work...");
    }
}

工厂型

还可以把 new 换掉,例如用一个工厂类来实现对象的创建,在 Spring 中很常见,例如,获取 bean

A a = A.getBean(____);
a.xxx();

具体实例:

...
  // 参数为字符串
  List<String> list = Arrays.asList("play", "work");
  list.forEach(s -> {
      A a = A.getBean(s);
      a.doing();
  });

  // 参数为Class
  List<? extends Class<? extends A>> list2 = Arrays.asList(Play.class, Work.class);
  list.forEach(s -> {
      A a = A.getBean(s);
      a.doing();
  });
...

// 工厂类
public abstract class A {
    protected abstract void doing();

    // 工厂方法: 传参字符串
    public static A getBean(String s) {
        A r = null;
        switch (s) {
            case "play":
                r = new Play();
                break;
            case "work":
                r = new Work();
                break;
            default:
                break;
        }
        return r;
    }

    // 工厂方法:传参 Class
    public static A getBean(Class<A> clz) throws InstantiationException, IllegalAccessException {
        if (clz == null) {
            return null;
        }
        return clz.newInstance();
    }
}

代理型

可以直接传参到代理类里面

A a = new A(____);
a.xxx();

具体示例:

...
C p = new C(new Play());
C w = new C(new Work());
p.doing();
w.doing();
...

public class C {
    private A a;
    public C(A obj) {
        this.a = obj;
    }
    public void doing() {
        this.a.doing();
    }
}

六大原则

使用设计模式时要遵循六大设计原则

原则 缩写 特点 备注
开闭原则(Open Close Principle) OCP 对扩展开放,对修改关闭 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果,易于维护和升级,需要使用接口和抽象类
里氏代换原则(Liskov Substitution Principle) LSP 任何基类可以出现的地方,子类一定可以出现 LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为
依赖倒转原则(Dependence Inversion Principle) DIP 针对接口编程,依赖于抽象而不依赖于具体 实体类继承抽象类,或实现接口
接口隔离原则(Interface Segregation Principle) ISP 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度 实现多个不同的接口,每个接口都有相对独立的功能
迪米特法则,又称最少知道原则(Demeter Principle) DP 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立 就像直线领导管理下级,避免高层领导跨级指挥
合成复用原则(Composite Reuse Principle) CRP 尽量使用合成/聚合的方式,而不是使用继承 继承毕竟有限制(单继承),聚合就会很灵活

总结

再来回顾一遍,我们用完形填空的方式来一步步探索实现,结果不经意间就触发了设计模式,那是因为设计模式也是在实践中总结出来的优秀经验,当我们朝着正确的方向优化改进代码,用上设计模式就是自然而然的事情,也就不足为奇了

类型 形式 备注 原则
传统型 A a = new A();
a.xxx();
通用形式
方法型 A a = new A();
a.__();
不同方法 违背 开闭原则、迪米特法则,对扩展不友好
方法参数型 A a = new A();
a.xxx(__);
同方法,不同参数 再改改就可成为工厂模式
单一职责型 __a = new__();
a.xxx();
不同类 用到 迪米特法则(DP)
多态型 A a = new __();
a.xxx();
同抽象,不同实现类 用到 开闭原则(OCP),依赖倒转(DIP),接口隔离(ISP),里氏代换(LSP) 等原则
工厂型 A a = A.getBean(__);
a.xxx();
统一用工厂类产生对象 用到 依赖倒转原则(DIP)
代理型 A a = new A(__);
a.xxx();
同方法,不同代理类 用到 合成复用原则(CRP)
完全型 __ a = __ __(__);
a.__(__);
各种设计模式可以相互组合 六大原则都可以用上
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐