设计模式笔记

1.类与类之间的关系

依赖,泛化,实现,关联,聚合,组合

1.1依赖关系(Dependence/Dependency)

只要是在类中用到了对方,那么他们之间就存在依赖关系,例如方法的返回类型,方法的参数类型,在方法中使用到的符号(虚箭头)。

1.2泛化关系(Ganeralization)

泛化关系也就是继承关系符号(带三角形的实线):

1.3实现关系(Realization)

实现关系就是依赖关系的特例,表示一个类实现了一个接口(interface)中定义的所有抽象方法。

1.4关联关系(Association)

类与类之间的关系,依赖关系的一种特例,通常通过成员变量来实现这种关系。

1.5聚合关系

关联关系的一种,聚合关系是整体和个体的关系,例如键盘、鼠标和键盘的关系。即聚合关系在关联关系的基础上,成员变量还是类的一部分。但是成员变量还可以独立访问。

1.6组合关系

关联关系的一种,整体和部分不能分开。和聚合关系的区别就在这里。

2.七大原则

2.1单一职责原则

  • 降低类的复杂度,一个类只负责一项职责
  • 提高类的可读性,可维护性
  • 降低更变引起的风险
  • 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以违反单一职责原则,只有类中方法数量足够少,可以在方法级别保持单一职责原则
java 复制代码
// 错误:一个类做两件事
class UserManager {
    void addUser(String name) { 
        System.out.println("添加用户:" + name); 
    }
    void sendEmail(String email) { // ❌ 发送邮件是另一件事
        System.out.println("发邮件到:" + email);
    }
}

// 正确:拆分成两个类
class UserService { // 只负责用户管理
    void addUser(String name) { 
        System.out.println("添加用户:" + name); 
    }
}

class EmailService { // 只负责邮件
    void sendEmail(String email) { 
        System.out.println("发邮件到:" + email);
    }
}

2.2接口隔离原则

  • 客户端不应该依赖他不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
java 复制代码
// 错误:胖接口
interface Worker {
    void work();
    void eat();
    void code(); // 工人不需要会写代码
}

class Programmer implements Worker { // 必须实现所有方法
    public void work() { System.out.println("工作"); }
    public void eat() { System.out.println("吃饭"); }
    public void code() { System.out.println("写代码"); }
}

class Chef implements Worker { // 厨师也要实现code(),不合理!
    public void work() { System.out.println("做菜"); }
    public void eat() { System.out.println("吃饭"); }
    public void code() { throw new UnsupportedOperationException(); } // 厨师不会写代码
}

// 正确:拆分接口
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Codable { void code(); }

class GoodProgrammer implements Workable, Eatable, Codable { // 程序员全要
    public void work() { System.out.println("工作"); }
    public void eat() { System.out.println("吃饭"); }
    public void code() { System.out.println("写代码"); }
}

class GoodChef implements Workable, Eatable { // 厨师不需要会写代码
    public void work() { System.out.println("做菜"); }
    public void eat() { System.out.println("吃饭"); }
}

2.3依赖倒转原则

高层模块不要直接依赖低层模块,都依赖抽象接口。

java 复制代码
// 错误的:高层依赖低层
class Dog {
    void bark() { System.out.println("汪汪"); }
}
class Person1 {
    private Dog dog = new Dog(); // 直接依赖具体的狗
    void callPet() { dog.bark(); }
}

// 正确的:都依赖抽象
interface Pet { void sound(); } // 抽象接口
class Dog2 implements Pet { public void sound() { System.out.println("汪汪"); } }
class Cat implements Pet { public void sound() { System.out.println("喵喵"); } }

class Person2 {
    private Pet pet; // 依赖抽象
    Person2(Pet pet) { this.pet = pet; } // 通过构造器注入
    void callPet() { pet.sound(); }
}

2.4里氏替换原则

  • 子类中尽量不要重写父类的方法
  • 在适当的情况下,可以通过聚合、组合、依赖代替继承
java 复制代码
// 错误的:子类改变了父类行为
class Rectangle {
    int width;
    int height;
    
    void setWidth(int w) { width = w; }
    void setHeight(int h) { height = h; }
    int getArea() { return width * height; }
}

class Square extends Rectangle { // 正方形继承长方形 ❌
    void setWidth(int w) { 
        width = w; 
        height = w; // 改变了父类的逻辑!
    }
    void setHeight(int h) {
        width = h;
        height = h; // 改变了父类的逻辑!
    }
}

// 正确:用接口或抽象类
interface Shape { // 抽象
    int getArea();
}

class GoodRectangle implements Shape {
    int width, height;
    public int getArea() { return width * height; }
}

class GoodSquare implements Shape {
    int side;
    public int getArea() { return side * side; }
}

2.5迪米特原则

  • 也叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管有多复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息
java 复制代码
// 错误的:直接和陌生人说话
class A {
    B b = new B();
    C getC() { return b.getC(); } // A 通过 B 拿到了 C
    void doSomething() {
        getC().doWork(); // ❌ A 直接调用了 C 的方法,认识太多人了
    }
}

// 正确的:只和直接朋友说话
class GoodA {
    B b = new B();
    void doSomething() {
        b.doSomething(); // ✅ 我只告诉B做什么,不关心他怎么做的
    }
}

class GoodB {
    C c = new C();
    void doSomething() {
        c.doWork(); // B负责和C沟通
    }
}

class C {
    void doWork() { System.out.println("C在工作"); }
}

2.6合成复用原则

  • 尽量使用合成/聚合的方法,而不是使用继承
java 复制代码
// 错误的:过度使用继承
class Engine { void start() { System.out.println("引擎启动"); } }

class Car extends Engine { // ❌ 继承:汽车"是一个"引擎?不合理
    void drive() { 
        start(); // 继承引擎的方法
        System.out.println("汽车行驶");
    }
}

// 正确的:使用组合
class GoodCar {
    private Engine engine = new Engine(); // ✅ 组合:汽车"有一个"引擎
    
    void drive() {
        engine.start(); // 调用引擎的方法
        System.out.println("汽车行驶");
    }
    
    // 可以随时更换引擎
    void setEngine(Engine e) { this.engine = e; }
}

2.7开闭原则

当代码需要变化时,尽量通过扩展软件实体的行为来实现变换,而不是通过修改已有的代码来实现变化,简单来说,软件要像乐高积木一样,可以随意添加新功能,但不用修改原来的代码。

java 复制代码
// 扩展开放:新增支付方式不改原来的代码
interface Payment { // 抽象
    void pay();
}

class Alipay implements Payment { // 支付宝
    public void pay() { System.out.println("支付宝支付"); }
}

class WechatPay implements Payment { // 微信支付
    public void pay() { System.out.println("微信支付"); }
}

// 新增支付方式:不用改下面这个类
class BankPay implements Payment { // 新增银行支付
    public void pay() { System.out.println("银行支付"); }
}

class PaymentService {
    public void process(Payment payment) { // 依赖抽象
        payment.pay(); // 这里永远不用改
    }
}

3.单例模式

单例模式是一种创建型设计模式, 确保一个类只有一个实例, 并且自行实例化冰箱整个系统提供这个实例。

3.1懒汉式

java 复制代码
public class Singleton {
	private Singleton() {}
	private static Singleton instance = null;
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

减小项目内存开销,但是线程不安全。

java 复制代码
public class Singleton {
	private Singleton() {}
	private static Singleton instance = null;
	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

线程安全,但是每次获取单例对象的时候,都需要进行synchronized判断,性能降低

3.2饿汉式

java 复制代码
public class Singleton {
	private Singleton() {}
	private static Singleton instance = new Singleton();
	public static Singleton getInstance() {
		return instance;
	}
}

增加系统开销

3.3双检锁

java 复制代码
public class Singleton {
	private Singleton() {}
	private static Singleton instance = null;
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}				
			}
		}
		return instance;
	}
}

由于instance = new Singleton()在指令界面不是一个原子性操作,即先进行分配内存,再进行初始化对象,再将对象指向内存地址。由于jvm可能对指令进行重排序,会先进行初始化地址,再进行指令指向地址对象。此时另一个线程依然会创建Single对象。

java 复制代码
public class Singleton {
	private Singleton() {}
	private volatile static Singleton instance = null;
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}				
			}
		}
		return instance;
	}
}

推荐

3.4静态内部类

java 复制代码
public class Singleton {
	private static class SingleonHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	private Singleton() {}
	public static final Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

能被反射破坏

3.5枚举类

java 复制代码
public enum Singleton {
	INSTANCE;
}
相关推荐
ss2732 小时前
线程池工作机制:从任务提交到执行的完整决策流程
java·开发语言
yaoxin5211232 小时前
276. Java Stream API - 使用 flatMap 和 mapMulti 清理数据并转换类型
java·开发语言·windows
Vic101012 小时前
【无标题】
java·数据库·分布式
摇滚侠2 小时前
Java 零基础全套视频教程,异常,处理异常,自定义异常,笔记 124-129
java·笔记
伯明翰java2 小时前
【无标题】springboot项目yml中使用中文注释报错的解决方法
java·spring boot·后端
企微自动化2 小时前
企业微信二次开发:深度解析外部群主动推送的实现路径
java·开发语言·企业微信
_修铁路的3 小时前
【Poi-tl】 Word模板填充导出
java·word·poi-tl
武子康3 小时前
Java-216 RocketMQ 4.5.1 在 JDK9+ 从0到1全流程启动踩坑全解:脚本兼容修复(GC 参数/CLASSPATH/ext.dirs)
java·大数据·分布式·消息队列·系统架构·rocketmq·java-rocketmq
austin流川枫3 小时前
🔥MySQL的大表优化方案 (实战分享)
java·mysql·性能优化