设计模式三大类详解:创建型、结构型、行为型模式及其应用原则
行为型模式主要关注对象之间的交互和职责分配。

创建型模式、结构型模式、行为型模式是常见的面向对象设计原则。其中有 1、迪米特法则,它又称最少知道原则(Demeter Principle)。
最少知道原则的含义是:一个实体应尽量减少与其他实体的相互作用,以让系统的功能模块保持相对独立。
从依赖者的角度来说,只依赖应该依赖的对象。
从被依赖者的角度说,只暴露应该暴露的方法。降低耦合
2、里氏代换原则
基类与子类之间存在着一定的关系。子类具备扩展父类功能的能力,然而却不能对父类原有的功能进行改变。具体而言:当子类继承父类时,除了添加新的方法来实现新增功能之外,应尽量避免对父类的方法进行重写。
依赖倒置原则指的是一种原则,它强调在设计和编程中,应该依赖于抽象而不是具体的实现。具体来说,就是高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于具体细节,具体细节应该依赖于抽象。这样可以提高代码的可维护性、可扩展性和可测试性。
它降低了客户与实现模块之间的耦合。
在软件设计中,细节具有多变的特点,抽象层相对稳定。所以,以抽象为基础搭建的架构比以细节为基础搭建的架构更稳定。这里的抽象指的是接口或者抽象类,细节指的是具体的实现类。
使用接口或抽象类的目的在于制定规范和契约,不涉及具体操作,将展现细节的任务交给它们的实现类去完成。
接口隔离原则,即 Interface Segregation Principle 。它强调将臃肿庞大的接口拆分成多个专门的小接口,每个接口只负责处理特定的行为或职责,而不是将所有的行为都放在一个大接口中。这样可以降低接口的复杂性,提高系统的可维护性和可扩展性。同时,也能更好地满足不同客户类的需求,避免客户端依赖于它不需要的方法。
这个原则意味着:相比使用单个接口,使用多个隔离的接口是更好的。它还意味着:能够降低类之间的耦合度。
5、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。
当程序需要拓展时,不能对原有代码进行修改,以实现热插拔的效果。也就是说,这样做是为了让程序具有良好的扩展性,便于维护和升级。要达到这样的效果,我们就得使用接口和抽象类。
单一原则就是复合复用原则。
一个类单一职责,防止耦合
设计模式分类:
创建型模式包含工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式,其中还有简单工厂模式。
结构型模式包含代理模式、装饰器模式、适配器模式、外观模式、组合模式、享元模式、桥接模式。
行为型模式包含观察者模式、责任链模式、模板方法模式、策略模式、迭代子模式、命令模式、状态模式、备忘录模式、访问者模式、中介者模式、解释器模式。
创建型模式工厂模式
工厂模式是一种创建对象的设计模式。它可以通过一个工厂类来创建对象,而不是直接使用 new 关键字。
// 产品接口
interface Product {
String sale();
}
// 具体产品A
产品 A 这个类实现了 Product 接口。
@Override
public String sale() {
return "Sale ProductA";
}
}
// 具体产品B
产品 B 类实现了产品接口。
@Override
public String sale() {
return "Sale ProductB";
}
}
// 工厂类
class ProductFactory {
创建一个产品的静态方法,该方法接收一个字符串类型的产品类型作为参数,用于创建产品。
如果产品类型等于“A”(不区分大小写),那么……
return new ProductA();
如果 productType 等于 "B"(不区分大小写),那么就执行以下操作。
return new ProductB();
}
}
}
// 客户端代码
public class Client {
公共静态的 void 主方法(String 数组 args)。
Product productA 是由 ProductFactory 创建的产品,其类型为 "A"。
输出 productA 的销售结果。
Product productB 是由 ProductFactory 创建的产品,其类型为 "B"。
System 显示 productB 的销售信息。
}
}
抽象工厂模式:
// 抽象产品:饮料
interface Beverage {
String serve();
}
// 抽象产品:小吃
interface Snack {
String serve();
}
// 具体产品:咖啡
咖啡类实现了饮料类。
public String serve() {
return "Serving Coffee";
}
}
// 具体产品:茶
Tea 是一种饮料,它实现了 Beverage 接口。
public String serve() {
return "Serving Tea";
}
}
// 具体产品:薯片
Chips 属于 Snack 这一类别。Chips 具备 Snack 的相关特性。Chips 被归类为 Snack 。
public String serve() {
return "Serving Chips";
}
}
// 具体产品:饼干
Cookies 是 Snack 中的一种。
public String serve() {
return "Serving Cookies";
}
}
// 抽象工厂
- 具备饮料相关的功能和操作。
Beverage createBeverage();
Snack createSnack();
}
// 具体工厂:咖啡和薯片工厂
咖啡和薯片工厂属于食品和饮料工厂的范畴。
创建一个饮料的方法是 public Beverage createBeverage()。 这个方法用于创建一个饮料对象。 它会执行一些特定的操作来初始化饮料的属性或状态。 最终返回创建好的饮料对象。
return new Coffee();
}
public Snack createSnack() {
return new Chips();
}
}
// 具体工厂:茶和饼干工厂
TeaAndCookiesFactory 这个类实现了 FoodAndBeverageFactory 接口。
public Beverage createBeverage() {
return new Tea();
}
public Snack createSnack() {
return new Cookies();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
FoodAndBeverageFactory 这个对象被初始化为一个 CoffeeAndChipsFactory;
System.out.println(coffeeAndChipsFactory.createBeverage().serve()); // 输出: Serving Coffee
System.out.println("Serving Chips"); // 输出: Serving Chips
FoodAndBeverageFactory 拥有一个名为 teaAndCookiesFactory 的对象,这个对象是通过 new TeaAndCookiesFactory() 创建的。
// 输出: 提供茶
// 输出: Serving Cookies
}
}
工厂模式和抽象工厂模式均为常用的设计模式,主要是用于对象的创建。它们存在一些相似的地方,然而也有着显著的差别。
共同点在于封装对象的创建:它们都通过这种方式,把客户端代码和具体的实现类分隔开来。
使用工厂方法来创建对象,而非直接使用 new 关键字。
遵循开闭原则:当需要增加新的产品时,能够做到在不改动客户端代码的前提下,借助增加新的产品类去对系统进行扩展。
工厂类承担对象创建的职责,客户端则负责对象的使用。 工厂类的职责是负责对象的创建,而客户端的职责是负责对象的使用。 工厂类专注于对象的创建,客户端专注于对象的使用。 工厂类的工作是负责对象的创建,客户端的工作是负责对象的使用。 工厂类主要负责对象的创建,客户端主要负责对象的使用。
不同点产品类型:接口设计:建造者模式(Builder)
在 Java 里,建造者模式属于一种创建对象的设计模式。这种模式能够允许通过多个步骤去构建一个较为复杂的对象。
传统的 set 和 get 方法用于对象装配,与之相比,引入静态内部类的构建者模式具有几个显著优点:
可读性和可维护性:
构建者模式能让对象的创建过程更为清晰,特别是在需要设置多个属性的情况下。通过链式调用能够提升代码的可读性。
不可变性:
构建者模式通常会返回一个对象,这个对象是不可变的。通过返回不可变对象,能够避免在对象创建之后被意外地修改,从而增强了对象的安全性。
简化复杂对象的创建:
对于有多个参数的对象,构建者模式能够给出默认值,这样就避免了构造函数参数数量过多的情况。
灵活性:
构建者模式能在创建对象时运用不同的参数组合,并且无需创建多个构造函数。
分离构建和表示:
构建者模式能把对象的构建过程和其表示分离开来,这样一来,相同的构建过程就能够创建出不同的表示。
支持可选参数:
在构建者模式里,能够很轻松地去处理那些可选参数,并且不需要通过在构造函数中使用重载的方式来处理。
public class Person {
private final String name;
private final int age;
private final String address;
一个私有的 Person 类的构造函数接收一个 Builder 对象作为参数。这个构造函数用于创建 Person 对象。在构造函数内部,通过 Builder 对象的属性来初始化 Person 对象的相应属性。
this.name = builder.name;
this.age = builder.age;
this 的地址等于 builder 的地址。
}
public static class Builder {
private String name;
private int age;
private String address;
公共的构建器,其作用是为名称进行设置。通过这个构建器,可以传入一个名为 name 的字符串参数,从而对名称进行相关的操作和设置。
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
公共的构建器(Builder)用于设置地址(address)。它接收一个字符串类型的地址参数。通过这个方法,可以对构建器的地址属性进行设置。
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
@Override
public String toString() {
使用 String.format 方法,将 name、age 和 address 等信息按照特定格式进行拼接,最终返回一个字符串,该字符串的格式为"Person{name='具体的 name 值', age=具体的 age 值, address='具体的 address 值'}"
}
}
public class Main {
public static void main(String[] args) {
创建一个 Person 的实例,通过 Person.Builder()来进行构建操作。
.name("John Doe")
.age(30)
.address("123 Main St")
.build();
System.out.println(person);
}
}
当面对创建过于复杂的对象时,如果不想对外暴露所有的创建方式,那么可以为构造者提供默认参数,只将必要参数接口对外暴露,以此来简化对象的创建。
原型模式
原型模式属于创建型设计模式。它主要的用途是通过对已有的对象进行克隆来创建新的对象。这样做能够提高性能,同时还可以避免构造函数所带来的约束。以下是关于原型模式的一些关键要点:
意图:通过复制一个已经存在的对象(原型)来创建新对象。
主要要解决的问题是,规避使用构造函数来创建对象所带来的复杂性,特别是当对象的创建成本比较高的时候。
关键代码:通常涉及到实现一个克隆方法,允许对象复制自身。
使用场景:
当一个类的实例化成本较高时。
当需要创建多个相似对象时。
浅拷贝与深拷贝:
浅拷贝:只复制对象的基本数据类型和引用类型的引用。
深拷贝的操作是复制对象以及它所引用的所有对象,这样能确保它们是完全独立的。需要继承 Cloneable 接口,然后重写 clone 方法,并且为复制的对象创建新的属性。同时还可以通过序列化来实现深拷贝。