(注意:代码均存放在GitHub仓库,希望拉取源码学习时点一个星标,文章中也有源码)
https://github.com/yang20040/design_patterns
(这是b站黑马设计模式 的笔记整理以及加入了我自己的理解整理出来的文章)
目录
目的:
为了提高系统的可维护性,可复用性,扩展性和灵活性,从而提高开发效率,节约软件开发成本和维护成本.
六大设计原则:
开闭原则:
定义:
对扩展开放,对修改关闭.在程序功能进行扩展时,不修改原有代码,为了程序的扩展性好.
如何实现:接口和抽象类.抽象的灵活性好,适用性广.合理的抽象能保持软件架构的稳定.当软件需求发生变化时,我们重新派生一个实现类来扩展就好了,软件中易变的细节可以通过派生的实现类进行扩展.
举个例子:(搜狗输入法)
搜狗输入法的皮肤设计:皮肤由背景图片,窗口颜色和声音等元素组成.
不同皮肤的共同特点可以定义为一个抽象类,每个具体的皮肤是其子类,新皮肤只需要派生一个实现类即可,不同皮肤的选择只需要切换到不同的实现类即可.这样的设计就是满足开闭原则的
1.创建一个抽象AbstractSkin类
(这个抽象类的目的是抽取这些输入法的共性,如背景图片,窗口颜色等)
2.创建一个抽象方法dispaly();抽象方法不需要方法体
3.创建一个DeafultSkin类继承AbstractSkin类并重写方法
打印"默认皮肤"
4.创建一个MySkin类继承重写方法
打印"我的皮肤"
(这体现了开闭原则,我们扩展功能只需要派生一个类即可)
5.定义一个类SougouInput用来聚合
- 先用构造器注入一个AbstractSkin对象
- 创建一个display方法
(用来链接皮肤和客户端)
6.使用搜狗输入法:
创建一个Client(客户端)表示搜狗输入法,调用SougouInput使用不同的皮肤
具体表现:
SougouInput用来链接启动类和不同皮肤,启动类只需要创建SougouInput对象和对应的皮肤对象,将皮肤对象传入SougouInput,这样Client想使用新皮肤时,只需要修改一行代码
Myskin->其它Skin
同时SougouInput也可以补充一些其它的逻辑
例如:
package com.henu.principles.designPrinciples.OpenClosed;
public class SougouInput {
private AbstractSkin skin;
public void setSkin(AbstractSkin skin) {
this.skin = skin;
// 可以在这里添加皮肤初始化逻辑
System.out.println("皮肤初始化完成");
}
public void display() {
if (skin != null) {
// 可以在这里添加皮肤切换动画逻辑
System.out.println("正在切换皮肤...");
skin.display();
}
}
}
源代码如下:
源代码地址:https://github.com/yang20040/design_patterns
抽象类:
package com.henu.principles.designPrinciples.OpenClosed;
public abstract class AbstractSkin {
public abstract void display();
}
客户端:相当于搜狗的启动类
package com.henu.principles.designPrinciples.OpenClosed;
public class Client {
public static void main(String[] args) {
MySkin mySkin = new MySkin();
SougouInput sougouInput = new SougouInput();
sougouInput.setSkin(mySkin);
sougouInput.display();
}
}
默认皮肤
package com.henu.principles.designPrinciples.OpenClosed;
public class DeaufltSkin extends AbstractSkin{
@Override
public void display() {
System.out.println("默认皮肤");
}
}
自创皮肤:
package com.henu.principles.designPrinciples.OpenClosed;
public class MySkin extends AbstractSkin{
@Override
public void display() {
System.out.println("MySkin");
}
}
中间类
package com.henu.principles.designPrinciples.OpenClosed;
public class SougouInput {
private AbstractSkin skin;
public void setSkin(AbstractSkin skin)
{
this.skin = skin;
}
public void display()
{
skin.display();
}
}
里氏代换原则:
定义:
任何基类出现的地方,子类一定可以出现.
简单理解:子类继承父类后可以扩展新的功能(方法),但尽量不要重写父类的方法
原因:1.子类重写了父类的方法,父类的方法写出来有什么意义?
2.父类要求子类重写方法时,会将方法定义为抽象方法
3.重写父类方法完成新功能虽然简便,但在多态使用频繁时容易出错
例如:(正方形不是长方形)
在数学领域,正方形是长方形的特例,属于长方形
Rectangle:长方形,分别设置了长和宽
Square:正方形,继承长方形并重写设长宽方法(因为正方形长宽相等)
RectangleDemo:
resize(拓宽长方形的宽使它大于长方形的长)
printLengthAndWidth(打印长宽)
源代码:
根据类图写代码
1.创建一个里氏代换例子包
2.创建长方形,包括成员变量和get,set方法,通过alt+insert快速生成
3.创建正方形,继承长方形重写方法
重写设置长宽,调用父类方法设置长宽,长宽必须相等
4.常见RectangleDemo来测试里氏替换
- psvm快捷键创建main类
- 创建resize拓宽方法
- 创建printLengthAndWidth方法
- main方法中分别调用长方形和正方形测试resize方法
这是长方形类:
package com.henu.principles.designPrinciples.RichterSubstitution;
/**
* Created by ys on 2017/7/12.
*/
public class Rectangle {
private double width;
private double length;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
}
正方形类:
特点:长宽相等,set方法重写设置长或宽时均设置
package com.henu.principles.designPrinciples.RichterSubstitution;
/**
* @author ys
* @date 2025/7/27
*/
public class Square extends Rectangle{
public void setLength(double length)
{
super.setLength(length);
super.setWidth(length);
}
public void setWidth(double width)
{
super.setLength(width);
super.setWidth(width);
}
}
测试类:
长方形使用时正常,正方形错误 使得程序死循环
原因在这段代码,设置宽时会把长设置与宽相等,导致一直是宽度增加
package com.henu.principles.designPrinciples.RichterSubstitution;
/**
* @author ys
* @date 2025/7/27
*/
public class RectangleDemo {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setLength(10);
rectangle.setWidth(5);
resize( rectangle);
printLengthAndWidth(rectangle);
System.out.println("-----------------");
Square square = new Square();
square.setLength(10);
resize(square);
printLengthAndWidth(square);
}
public static void resize(Rectangle rectangle){
//长大于宽时,宽+1(循环请求)
while (rectangle.getLength() >= rectangle.getWidth()){
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println("长:" + rectangle.getLength());
System.out.println("宽:" + rectangle.getWidth());
}
}
结论:
根据历史代换原则,子类必须能替代父类所有的基类,因此,正方形不属于长方形
改进方案:
我们要让长方形和正方型符合里氏替换原则,长方形和正方形没有继承关系,但获取长宽的方法是共性,我们可以抽象为一个接口(一种规范),这样我们既复用了代码,又可在使用长方形拓宽方法时不会造成潜在的死循环错误.
重新设计类图
让长方形和正方形共同抽取一个四边形接口
1.定义一个接口获得长宽
2.定义长方形
3.定义正方形
4.测试(测试结果如下,此时编译无法通过)
新源代码:
接口
package com.henu.principles.designPrinciples.RichterSubstitution.After;
/**
* @author ys
* @date 2025/7/27
*/
public class RectangleDemo {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setLength(10);
rectangle.setWidth(5);
resize( rectangle);
printLengthAndWidth(rectangle);
System.out.println("-----------------");
Square square = new Square();
square.setSide(10);
//测试
// resize(square);
// printLengthAndWidth(square);
}
public static void resize(Rectangle rectangle){
//长大于宽时,宽+1(循环请求)
while (rectangle.getLength() >= rectangle.getWidth()){
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println("长:" + rectangle.getLength());
System.out.println("宽:" + rectangle.getWidth());
}
}
长方形
package com.henu.principles.designPrinciples.RichterSubstitution.After;
public class Rectangle implements Quadrilateral{
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
}
正方形
package com.henu.principles.designPrinciples.RichterSubstitution.After;
public class Square implements Quadrilateral{
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
测试类
package com.henu.principles.designPrinciples.RichterSubstitution.After;
/**
* @author ys
* @date 2025/7/27
*/
public class RectangleDemo {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setLength(10);
rectangle.setWidth(5);
resize( rectangle);
printLengthAndWidth(rectangle);
System.out.println("-----------------");
Square square = new Square();
square.setSide(10);
//测试
// resize(square);
// printLengthAndWidth(square);
}
public static void resize(Rectangle rectangle){
//长大于宽时,宽+1(循环请求)
while (rectangle.getLength() >= rectangle.getWidth()){
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println("长:" + rectangle.getLength());
System.out.println("宽:" + rectangle.getWidth());
}
}
依赖倒置原则:
高层模块不依赖于低层模块,两者都应该依赖于其抽象,抽象不应该依赖于细节,细节应该依赖于抽象.要求是面向抽象编程,而不是面向实现编程,这样就降低了客户和模块之间的耦合.
看这张图:
A类的成员变量定义了一个B类的对象(A依赖了B),那么A类就是高层模块,B类就是低层模块,抽象就是将B类抽象为一个抽象父类或者父接口,B类继承或实现抽象父类或者父接口,而A类依赖于抽象父类或父接口.
举个例子:
1.springboot三层架构
controller
service
serviceImpl
controller层不直接依赖于serviceImpl,而是依赖于service,降低了模块之间的耦合度
2.组装电脑
组装一台电脑需要CPU,显卡,内存条,硬盘等,不同的配件又分为了不同的型号
硬盘有保存和获取数据的功能
CPU有运行功能
内存有保存数据的功能
根据类图创建项目源码
硬盘:
package com.henu.principles.designPrinciples.dependencyInversion.Before;
public class XiJieHardDisk {
public void save(String data)
{
System.out.println("使用希捷硬盘保存数据:" + data);
}
public String get()
{
System.out.println("使用希捷硬盘获取数据");
return "数据";
}
}
CPU:
package com.henu.principles.designPrinciples.dependencyInversion.Before;
public class IntelCpu {
public void run()
{
System.out.println("IntelCpu运行了");
}
}
内存条:
package com.henu.principles.designPrinciples.dependencyInversion.Before;
public class KingstonMemory {
public void save()
{
System.out.println("使用金士顿内存条");
}
}
电脑:
package com.henu.principles.designPrinciples.dependencyInversion.Before;
public class Computer {
private IntelCpu cpu;
private KingstonMemory memory;
private XiJieHardDisk hardDisk;
public IntelCpu getCpu() {
return cpu;
}
public void setCpu(IntelCpu cpu) {
this.cpu = cpu;
}
public KingstonMemory getMemory() {
return memory;
}
public void setMemory(KingstonMemory memory) {
this.memory = memory;
}
public XiJieHardDisk getHardDisk() {
return hardDisk;
}
public void setHardDisk(XiJieHardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public void run() {
System.out.println("电脑开始运行");
String data = hardDisk.get();
System.out.println("从硬盘中获取数据" + data);
cpu.run();
memory.save();
}
}
客户端:
package com.henu.principles.designPrinciples.dependencyInversion.Before;
public class ComputerDemo {
public static void main(String[] args)
{
IntelCpu cpu = new IntelCpu();
KingstonMemory memory = new KingstonMemory();
XiJieHardDisk hardDisk = new XiJieHardDisk();
Computer computer = new Computer();
computer.setCpu(cpu);
computer.setMemory(memory);
computer.setHardDisk(hardDisk);
computer.run();
}
}
问题:
这个代码不够灵活,电脑中写死了硬盘等配件的种类,客户想要修改配置,不仅要修改客户端的代码,还要修改电脑类的代码
改进方案:
抽象出一个接口,这样Computer的代码就不需要修改了,切换新配件只需要让新配件代替老配件去实现接口,在客户端修改创建的对象即可
新源代码:
结构:
代码:
接口
package com.henu.principles.designPrinciples.dependencyInversion.after;
public interface HardDisk {
public void save(String data);
public String get();
}
package com.henu.principles.designPrinciples.dependencyInversion.after;
public interface Cpu {
public void run();
}
package com.henu.principles.designPrinciples.dependencyInversion.after;
public interface Memory {
public void save();
}
实现类:
package com.henu.principles.designPrinciples.dependencyInversion.after;
public class XiJieHardDisk implements HardDisk{
public void save(String data)
{
System.out.println("使用希捷硬盘保存数据为:" + data);
}
public String get()
{
System.out.println("使用希捷硬盘获取数据");
return "数据";
}
}
package com.henu.principles.designPrinciples.dependencyInversion.after;
public class IntelCpu implements Cpu{
@Override
public void run() {
System.out.println("IntelCpu开始运行");
}
}
package com.henu.principles.designPrinciples.dependencyInversion.after;
public class KingstonMemory implements Memory{
@Override
public void save() {
System.out.println("Kingston内存条保存数据");
}
}
电脑(传入参数为接口)
package com.henu.principles.designPrinciples.dependencyInversion.after;
public class Computer {
private Cpu cpu;
private Memory memory;
private HardDisk hardDisk;
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public Memory getMemory() {
return memory;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public HardDisk getHardDisk() {
return hardDisk;
}
public void setHardDisk(HardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public void run()
{
System.out.println("电脑开始运行");
String data = hardDisk.get();
System.out.println("从硬盘中获取数据" + data);
cpu.run();
memory.save();
}
}
测试类
package com.henu.principles.designPrinciples.dependencyInversion.after;
public class ComputerDemo {
public static void main(String[] args) {
Cpu cpu = new IntelCpu();
Memory memory = new KingstonMemory();
HardDisk hardDisk = new XiJieHardDisk();
Computer computer = new Computer();
computer.setCpu(cpu);
computer.setMemory(memory);
computer.setHardDisk(hardDisk);
computer.run();
}
}
接口隔离原则:
定义:
客户端不应该被迫依赖于它不使用的对象,一个类对另一个类的依赖应该建立在最小接口上.
定义讲解:
问题:
(客户端不应该被迫依赖于它不使用的对象)
B类想使用A类的方法1,B类就继承了A类,但是B类不想使用方法2,那么它就被迫依赖于它不使用的对象
改进方案:
将A类两个功能拆为A接口和B接口,这样新接口只需要实现自己想要的方法的接口
例如:(安全门案例)
安全门具有防盗,防火,防水功能,我们可以自定义自己的安全门实现这个接口
问题是,我如果想要一个新的安全门,能防水火,不能防盗,如果实现上述接口,岂不是违背了最小接口原则
改进前源代码:
package com.henu.principles.designPrinciples.minimalInterface.Before;
public interface SafetyDoor {
void antiTheft();
void fireProof();
void waterProof();
}
package com.henu.principles.designPrinciples.minimalInterface.Before;
public class MySafetyDoor implements SafetyDoor{
@Override
public void antiTheft() {
System.out.println("防盗");
}
@Override
public void fireProof() {
System.out.println("防火");
}
@Override
public void waterProof() {
System.out.println("防水");
}
}
package com.henu.principles.designPrinciples.minimalInterface.Before;
public class SafetyDemp {
public static void main(String[] args) {
MySafetyDoor mySafetyDoor = new MySafetyDoor();
mySafetyDoor.antiTheft();
mySafetyDoor.fireProof();
mySafetyDoor.waterProof();
}
}
改进后源代码:

package com.henu.principles.designPrinciples.minimalInterface.After;
public interface AntiTheft {
void antiTheft();
}
package com.henu.principles.designPrinciples.minimalInterface.After;
public interface Fireproof {
void fireProof();
}
package com.henu.principles.designPrinciples.minimalInterface.After;
public interface Waterproof {
void waterProof();
}
package com.henu.principles.designPrinciples.minimalInterface.After;
public class MySafetyDoor implements AntiTheft, Fireproof, Waterproof{
public void antiTheft()
{
System.out.println("防盗");
}
public void fireProof()
{
System.out.println("防火");
}
public void waterProof()
{
System.out.println("防水");
}
}
package com.henu.principles.designPrinciples.minimalInterface.After;
public class NewSafetyDoor implements Fireproof, Waterproof{
@Override
public void fireProof() {
System.out.println("更防火");
}
@Override
public void waterProof() {
System.out.println("更防水");
}
}
package com.henu.principles.designPrinciples.minimalInterface.After;
public class SafetyDoorDemo {
public static void main(String[] args) {
MySafetyDoor mySafetyDoor = new MySafetyDoor();
mySafetyDoor.antiTheft();
mySafetyDoor.fireProof();
mySafetyDoor.waterProof();
NewSafetyDoor newSafetyDoor = new NewSafetyDoor();
newSafetyDoor.fireProof();
newSafetyDoor.waterProof();
}
}
迪米特法则(最小知识原则)
定义:
只和你的直接朋友交谈,不和"陌生人说话"
含义:
如果两个软件实体无须直接通信,尽量通过第三方转发(如租房通过中介,经纪人安排明星粉丝见面等,在软件行业要做软件的公司与软件公司对接,而不是工程师)
目的:
降低类之间的耦合度,提高模块独立性
"朋友":对象本身,成员变量,当前对象创建的对象,方法参数等
例如:(明星和经济人实例)
明星专注于演艺,经纪人负责粉丝见面会,媒体公司交涉等工作,这个例子中经纪人是明星的朋友,而粉丝是陌生人,适合迪米特法则
源代码:
package com.henu.principles.designPrinciples.dimmitLaw;
public class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package com.henu.principles.designPrinciples.dimmitLaw;
public class Fans {
private String name;
public Fans(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package com.henu.principles.designPrinciples.dimmitLaw;
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package com.henu.principles.designPrinciples.dimmitLaw;
public class Agent {
private Star star;
private Fans fans;
private Company company;
public void setStar(Star star) {
this.star = star;
}
public void setFans(Fans fans) {
this.fans = fans;
}
public void setCompany(Company company) {
this.company = company;
}
public void business()
{
System.out.println("明星:" + star.getName() + "和公司:" + company.getName() + "洽谈业务");
}
public void meeting()
{
System.out.println("明星:" + star.getName() + "和粉丝:" + fans.getName() + "见面");
}
}
package com.henu.principles.designPrinciples.dimmitLaw;
public class dimmitLawDemo {
public static void main(String[] args) {
Star star = new Star("高圆圆");
Company company = new Company("中国传媒公司");
Fans fans = new Fans("小王");
Agent agent = new Agent();
agent.setStar(star);
agent.setCompany(company);
agent.setFans(fans);
//商业活动
agent.business();
//粉丝见面
agent.meeting();
}
}
合成复用原则
定义:
尽量先用组合或者聚合等关联关系实现复用,其次再考虑继承关系
继承:白箱复用
优点:
简单
缺点:
1.破坏了父类的封装性,父类内部细节暴露给子类
2.父类和子类的耦合度高,父类的任何改变都可能影响子类
3.限制了复用的灵活性,子类从父类继承的实现是静态的,在编译时已经确定,无法修改
组合和聚合:黑箱复用
将已有对象纳入新对象,使它作为新对象的一部分
优点:
1.维护了封装性,不知道已有对象的内部细节
2.对象之间耦合度第,可以在类的成员位置声明抽象
3.复用的灵活性高,(多态)方法参数可以动态传入抽象的子类对象或实现
例子:
原实现:
汽车按"动力源"电力和汽油,按"颜色"可以分为白,红等(继承关系如下)
定义新类添加子类非常多
改进方案:
添加新动力汽车时的对比:
源代码:
package com.henu.principles.designPrinciples.syntheticMultiplexing;
public abstract class Car implements Color{
public abstract void move();
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;
public interface Color {
void carColor();
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;
public class ElectricCar extends Car{
@Override
public void move() {
System.out.println("电力发电");
}
@Override
public void carColor() {
System.out.println("白色");
}
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;
public class PotrolCar extends Car{
@Override
public void move() {
System.out.println("柴油车");
}
@Override
public void carColor() {
System.out.println("红色");
}
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;
public class Demo {
public static void main(String[] args) {
Car car = new ElectricCar();
car.move();
car.carColor();
}
}