设计模式速通(一)
设计模式是解决软件设计中常见问题的可重用方案,核心在于处理对象创建、传递和方法调用。通过"完形填空"方式可快速理解设计模式的应用场景,如传统型、方法型、多态型等。关键技巧包括:抽象共性方法、使用多态传参、单一职责原则等。掌握这些基础模式后,可灵活组合应用,提高代码复用性和可维护性,避免"屎山代码"。
前言
设计模式 是软件设计中常见问题的可重用解决方案,它们是在长期实践中总结出来的最佳实践。用好设计模式可以 使代码逻辑清晰,简化、减少重复代码,提高编程效率。
别看设计模式有 23 种之多,无外乎是要处理好这几件事情:
- 创建对象
- 传递对象
- 调用方法
针对于这几件事情 玩出花 来了,就总结为设计模式。为此我还编过一首小诗来记住它们:
# 创建型
建原单例抽工厂, # 建造者,原型,单例,工厂,抽象工厂模式
# 结构型
适组过桥享代装, # 适配,(外观),组合,过滤器,桥接,享元,代理,装饰器模式
# 行为型
任命观状空备策, # 责任链,命令,观察者,状态,空对象,备忘录,策略模式
解释迭代中模访。 # 解释器,迭代器,中介者,模板,访问者模式
其实大可不必都记住它们,只要学会几个完形填空,用啥设计模式就呼之欲出了,下面用我的方式来速通它吧!
预告
只要会填下面各种完形填空,你就会用设计模式了,信不信?
| 类型 | 形式 | 备注 |
|---|---|---|
| 传统型 | 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...");
}
}
多态型
Play 与 Work 都有 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.__(__); |
各种设计模式可以相互组合 | 六大原则都可以用上 |
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)