设计模式
七大原则
开闭原则
对于修改关闭,对扩展开放
我们现在有一个Human接口,有老师和学生对于Human的实现,后续如果需要拓展学生和老师,例如初中老师,高中学生,就可以继承于老师或者学生类,继承默认的学生、老师方法,有需要修改的直接重写方法或者增添方法即可。而无需修改老师和学生的实现类
依赖倒转原则
依赖倒转是开闭原则的基础,针对于接口进行编程
依赖抽象而不是具体的实现,可以减少类的耦合性,提升稳定性,提高代码的可读性和维护性,降低修改程序造成的风险
单一原则
一个类、接口、方法、只负责一项职责
降低程序复杂性,提高了可维护性,降低了变更带来的风险
例如我们有一个Java程序员继承自程序员抽象类,其中实现了吃饭的抽象方法,我们现在要改为点外卖,我们最好创建一个点外卖的吃饭类,其中包含了堂食和外卖两种方法,将点饭类放入程序员类中,由Java程序员来进行调用
接口隔离原则
用多个接口,而不是使用单一接口
尽量细化接口,其中的方法尽可能减少
符合低耦合设计思想,提高了可拓展性和可维护性
迪米特法则
最少知道原则:一个对象应该对于其他对象保持最少的了解,降低类和类之间的耦合性,强调之和朋友交流,不和陌生人说话
里氏替换原则
是继承复用的基石,也是对于开闭原则的补充
子类可以拓展父类的功能,但是不能修改父类原有的功能,
子类可以实现父类的抽象方法,但是不能覆盖原有的父类方法
对于子类的继承关系进行约束
增加程序健壮性
合成复用原则
尽量使用组合聚合的方式进行对象的使用,而不是使用继承关系达到软件复用的目的
可以使系统更加灵活,降低类与类之间的耦合度
设计模式
设计模式的类型分为三种
创建型模式
隐藏了创建对象的过程,通过逻辑方法创建对象,而不是new关键字
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式
主要关注类和对象的组合关系,集成的概念被用于组合接口和定义组合对象,从而获得新功能方式
适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式
行为型模式
主要关注对象之间的通信
责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式
工厂模式
简单工厂
是工厂方法模式的一种特殊实现,又称为静态工厂方法模式
有三个角色:抽象产品,具体产品,工厂类
具体产品
public class Fighter extends Weapon{
@Override
public void attack() {
System.out.println("飞机攻击");
}
}public class Tank extends Weapon{
@Override
public void attack() {
System.out.println("坦克攻击");
}
}抽象产品
public abstract class Weapon {
public abstract void attack();
}工厂类
public class WeaponFactory {
public static Weapon get(String weaponType) {
if ("Tank".equals(weaponType)) {
return new Tank();
} else if ("Fighter".equals(weaponType)) {
return new Fighter();
} else {
System.out.println("创建失败");
return null;
}
}
}测试程序
public class Test {
public static void main(String[] args) {
Weapon tank = WeaponFactory.get("Tank");
if (tank != null) {
tank.attack();
}
}
}优点:
客户只负责消费,工厂类负责生产,生产者和消费者分离,客户端不需要关注实现细节,只负责传入索要即可使用
缺点:
增加工厂产品,需要修改工厂类代码,违反OCP原则,另外工厂的责任大,发生问题的时候系统瘫痪,健壮性差
工厂方法模式
一个产品对应一个工厂
解决了简单工厂模式中出现的OCP问题
有四个角色:抽象产品,具体产品(多个),抽象工厂类,具体工厂类(多个)
抽象工厂
public abstract class WeaponFactory {
public abstract Weapon get();
}具体工厂
public class TankFactory extends WeaponFactory{
@Override
public Weapon get() {
return new Tank();
}
}测试程序
public class Test {
public static void main(String[] args) {
WeaponFactory factory = new TankFactory();
Weapon tank = factory.get();
tank.attack();
}
}添加新的产品的时候,只需要增加Weapon的实现类,以及对应武器的工厂实现类即可在不影响原有代码的基础上,通过对应的具体工厂创建武器的实例
缺点:类的复杂性增加,类的数量大
抽象工厂
抽象工厂提供了一系列相关或相互依赖对象的接口
定义按钮和文本框的通用接口
interface Button {
void render();
void onClick();
}
interface TextBox {
void render();
void input(String text);
}实现具体产品,例如Windows风格的按钮和文本框
class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Windows 风格按钮渲染");
}
@Override
public void onClick() {
System.out.println("Windows 按钮点击");
}
}
class WindowsTextBox implements TextBox {
@Override
public void render() {
System.out.println("Windows 风格文本框渲染");
}
@Override
public void input(String text) {
System.out.println("Windows 文本框输入: " + text);
}
}定义一个抽象工厂接口,其中规定了返回两个组件接口的方法
interface GUIFactory {
Button createButton();
TextBox createTextBox();
}继承接口实现方法,返回Window类型的各个组件
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public TextBox createTextBox() {
return new WindowsTextBox();
}
}这样通过这个实现抽象工厂的Windows工厂实例去创建对应的各个组件了
定义客户端
我们对于这个工程再次进行封装,提取出抽象工厂作为接口,这样我们在传入工厂实例的时候就可以通过实例初始化各个组件了
public class Application {
private final Button button;
private final TextBox textBox;
public Application(GUIFactory factory) {
button = factory.createButton();
textBox = factory.createTextBox();
}
public void renderUI() {
button.render();
textBox.render();
}
public void simulateUserInput() {
button.onClick();
textBox.input("Hello, Abstract Factory!");
}
public static void main(String[] args) {
// 使用 Windows 风格
Application windowsApp = new Application(new WindowsFactory());
windowsApp.renderUI();
windowsApp.simulateUserInput();
}后续我们扩展新的风格只需要:
1.创建Button和TextBox的实现类,2.新风格的工厂实例
在客户端中传入工厂实例即可
建造者模式
通过静态内部类Builder链式的设置其各个属性,通过build方法返回实例对象
public class Student {
private String name;
private int age;
public Student(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
public static class Builder{
private String name;
private int age;
public Builder name(String name){
this.name = name;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Student build(){
return new Student(this);
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}public class Main {
public static void main(String[] args) {
Student lory = new Student.Builder().
age(18).
name("lory").
build();
System.out.println(lory);
}
}将复杂对象的创建和属性分离,建造的过程和细节我们不需要知道,只需要通过构建者进行创建
原型模式
用于创建重复的对象,我们需要保证创建对象的性能
原型设计模式是创建对象的最佳方式,通过克隆现有对象来创建新的对象,避免重复初始化成本,适合用于创建开销大,或需要动态配置对象的场景
public class Pig {
private String name;
private String ability;
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", ability='" + ability + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAbility() {
return ability;
}
public void setAbility(String ability) {
this.ability = ability;
}
}每一次创建新的对象都要重新new对象,设置参数
public class Main {
public static void main(String[] args) {
Pig pig1 = new Pig();
pig1.setName("佩奇");
pig1.setAbility("睡觉");
System.out.println(pig1);
Pig pig2 = new Pig();
pig2.setName("乔治");
pig2.setAbility("吃饭");
System.out.println(pig2);
}
}现在我们通过原型设计模式对于这个类进行改造
实现Cloneable接口并实现其中方法
package com;
public class Pig implements Cloneable {
private String name;
private String ability;
@Override
protected Pig clone() {
try {
return (Pig) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", ability='" + ability + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAbility() {
return ability;
}
public void setAbility(String ability) {
this.ability = ability;
}
}使用原先的pig,clone浅拷贝一份对象出来
public class Main {
public static void main(String[] args) {
Pig pig1 = new Pig();
pig1.setName("佩奇");
pig1.setAbility("睡觉");
System.out.println(pig1);
Pig pig2 = pig1.clone();
// Pig pig2 = new Pig();
pig2.setName("乔治");
pig2.setAbility("吃饭");
System.out.println(pig2);
}
}NOTE浅拷贝和深拷贝
| 场景 | 推荐方式 |
|---|---|
对象的所有字段是基本类型或不可变类(如 String、Integer) | 浅拷贝(安全) |
对象包含可变引用类型(如 List、Map、自定义类) | 深拷贝(避免共享问题) |
| 需要极高的性能,且确定引用字段不会被修改 | 浅拷贝(谨慎使用) |
单例模式
双重检查
通过volatile和两次为null判断和synchronized关键字实现
public class Person {
private static volatile Person person = null;
private Person() {}
public static Person getInstance() {
if (person == null) {
synchronized (Person.class) {
if (person == null) {
person = new Person();
}
}
}
return person;
}
}volatile 的必要性
person = new Person();不是原子操作,它分为了三个部分
1.分配内存空间
2.初始化对象
3.将内存地址赋给person
如果没有进行volatile那么JMV可能会继续指令重排(1.3.2),导致其他线程没有拿到初始化的对象
确保一个线程对person的修改,对其他线程立即可见
静态内部类
通过在单例类中,通过私有的静态内部类,创建单例对象
public class Person {
private static class Inner {
private static final Person person = new Person();
}
private Person() {}
public static Person getInstance() {
return Inner.person;
}
}这里使用Person中的Inner内部类的Inner.person时候才会进行加载Inner类,实现了懒加载
饿汉式
public class Person {
private static final Person person;
static {
person = new Person();
}
private Person() {}
public static Person getInstance() {
return person;
}
}枚举
枚举方式实现单例模式,是最佳的实现方式,可以有效防止对于单例模式的破坏
public enum Person {
INSTANCE;
public static Person getInstance() {
return INSTANCE;
}
}public enum Person {
INSTANCE;
private String name;
// 枚举构造器(天生 private,不可 public/protected)
Person() {
this.name = "Default";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 使用
Person.INSTANCE.setName("Alice");
System.out.println(Person.INSTANCE.getName()); // 输出 "Alice"单例破坏
序列化破坏
public class Main {
public static void main(String[] args) throws Exception{
Person p1 = Person.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sin"));
oos.writeObject(p1);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("sin"));
Person p2 = (Person) ois.readObject();
System.out.println(p1.hashCode() + "\n" + p2.hashCode());
}
}- Java 在反序列化
enum时,不会调用构造方法,而是 直接返回 JVM 维护的唯一实例(类似readResolve()的逻辑)。 enum的序列化仅存储name,反序列化时根据name查找已有实例(Enum.valueOf)
反射破坏
public class Main {
public static void main(String[] args) throws Exception{
Person p1 = Person.getInstance();
Constructor<Person> constructor = Person.class.getDeclaredConstructor();
constructor.setAccessible(true);
Person p2 = constructor.newInstance();
System.out.println(p1.hashCode() + "\n" + p2.hashCode());
}
}Java 不允许通过反射创建 enum 实例:
enum的构造器默认 private,且 JVM 进行严格限制。- 即使 反射强行调用
enum的构造器,也会抛出IllegalArgumentException
克隆破坏
Person p1 = Person.getInstance();
Person p2 = p1.clone(); // ❌ 创建新对象,破坏单例enum类的clone方法由final修饰,禁止重写