设计模式之禅
目录
示例代码
6大设计原则
单一职责原则
单一职责原则(Single Responsibility Principle,SRP)
定义
应该有且仅有一个原因引起类的变更。
解释
要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情。
优点
- 类的复杂性降低,实现什么职责都有清晰明确的定义;
- 可读性提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,可读性提高,那当然更容易维护了;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
缺点
纯理论地来讲,这个原则是非常优秀的,但是现实有现实的难处,你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。
注意
对于接口,我们在设计的时候一定要做到单一,类的设计尽量做到只有一个原因引起变化。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。
- 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)
定义
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
解释
重点是java的三大特征之一,“继承”。里氏替换原则强调,如果一个类型是一个子类型,那么它应该能够替换掉它的父类型,并且程序的行为不应该受到影响。
Java继承是面向对象编程中的一个重要概念。它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以继承父类的非私有成员,并且可以在子类中添加新的成员或重写父类的方法。
继承规范
- 子类必须完全实现父类的方法
- 子类可以有自己的个性
- 覆盖或实现父类的方法时输入参数可以被放大
- 覆写或实现父类的方法时输出结果可以被缩小
优点
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性;
- 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
- 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
- 提高产品或项目的开放性。
- 实现多态和抽象的基础。
缺点
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
- 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
- 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。
注意
- 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
- 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
- 采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,非常完美!
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle, DIP)
定义
重点是“抽象”(表现为抽象类和接口),依赖倒置原则强调高层模块不应该依赖于低层模块的具体实现,而应该依赖于抽象。具体来说,模块之间的依赖关系应该通过抽象进行定义,而不是具体的实现类。这样可以降低模块之间的耦合度,提高代码的可扩展性和可维护性。
解释
依赖倒置原则的核心思想是面向接口编程。高层模块定义抽象接口,低层模块实现这些接口,而高层模块通过接口与低层模块进行交互。这样,高层模块就不需要直接依赖于低层模块的具体实现,而是依赖于抽象接口。 从而降低模块间的耦合度,提高代码的灵活性和可维护性。
优点
减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。同时也有助于实现开闭原则和单一职责原则。
缺点
- 增加系统复杂度、学习和理解成本
- 增加开发成本
- 运行时性能开销
- 难以处理复杂依赖关系
注意
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
- 变量的表面类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用
接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)
定义
客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。
解释
- 接口隔离原则强调客户端不应该依赖于它不需要使用的接口。具体来说,一个类对其他类的依赖应该建立在最小的接口上,而不是依赖于庞大而复杂的接口。这样可以降低类之间的耦合度,提高代码的可维护性和可重用性。
- 接口隔离原则的核心思想是将庞大的接口拆分为更小、更具体的接口,以满足客户端的实际需求。每个类只需要实现与其相关的接口,而不需要实现无关的接口。这样可以避免类因为实现无用接口而承担额外的责任和复杂性。
优点
通过遵循接口隔离原则,可以使系统更加灵活、可扩展和易于维护。它有助于减少代码的冗余和复杂度,提高代码的可读性和可测试性。同时,接口隔离原则也促进了接口的单一职责,使接口更加清晰和易于理解。
缺点
- 直观上增加接口数量,从而提高系统复杂度,进而导致系统维护成本和难度提高
- 设计和管理,增加开发的复杂度和学习成本
注意
- 接口要尽量小
- 接口要高内聚
- 定制服务
- 接口设计是有限度的
- 一个接口只服务于一个子模块或业务逻辑
- 通过业务逻辑压缩接口中的public方法
迪米特法则
迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP)
定义
一个对象应该对其他对象有最少的了解
解释
重点是“解耦”,根据迪米特法则,一个对象应该只与其直接的朋友进行交互,而不应该与朋友的朋友进行交互。这意味着一个对象只应该调用其成员变量、方法的参数、方法返回的对象的方法,而不应该直接访问其他对象的内部状态。
主要实现手段是访问权限控制:public
、protected
(继承访问权限)、private
(无权访问)、Serializable
(可序列化接口)、final
、package权限等
优点
遵循迪米特法则可以提高代码的可读性和可理解性,减少代码的冗余和复杂度。它也有助于实现单一职责原则和开闭原则。
缺点
产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。
注意
- 只和朋友交流,但朋友间也要有距离
- 如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
- 谨慎使用Serializable
开闭原则
开闭原则(Open-Closed Principle,OCP)
定义
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
解释
换句话说,当需要对系统进行扩展时,应该通过添加新的代码来实现,而不是修改已有的代码。这样可以保证系统的稳定性和可维护性。
遵循开闭原则的关键是通过抽象和多态来实现可扩展性。通过定义抽象的接口或基类(抽象类等),可以使系统对扩展开放。当需要添加新的功能时,只需要实现新的子类或衍生类,而不需要修改现有的代码。
注意
在实践中过程中,架构师或项目经理一旦发现有发生变化的可能,或者变化曾经发生过,则需要考虑现有的架构是否可以轻松地实现这一变化。架构师设计一套系统不仅要符合现有的需求,还要适应可能发生的变化,这才是一个优良的架构。
23种设计模式
单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
在单例模式中,类的构造函数是私有的,这样就防止了其他代码直接实例化该类。通过提供一个静态方法或属性来获取类的实例,其他代码可以使用该方法或属性来获取唯一的实例。
单例模式通常用于需要共享资源的场景,例如数据库连接、日志记录器等。通过使用单例模式,可以确保只有一个实例被创建和使用,避免了资源浪费和冲突。
实现
饿汉模式
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
- 静态常量
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
- 静态代码块
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
懒汉式
为了解决线程不安全问题,对getInstance()
方法进行了线程同步。
缺点:效率太低,每个线程获得类的实例时候,执行getInstance()
方法都要进行同步。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
双重检查(双检锁)
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)
检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null)
,直接return
实例化对象。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类
静态内部类方式在Singleton
类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance
类,从而完成Singleton
的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
枚举
仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
工厂模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
解释
工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,而无需将对象的具体类暴露给调用方。它通过定义一个共同的接口或基类来创建对象,然后由具体的工厂类来实现对象的创建。 工厂模式的主要目的是将对象的创建与使用分离,使得代码更加灵活、可扩展和可维护。通过使用工厂模式,可以在不修改现有代码的情况下,轻松添加新的产品类型或变体。
优点
- 首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。
再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
- 最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
实现
- 通用方式
// 产品
interface Product {
void operation();
}
// 具体产品A
class ConcreteProductA implements Product {
public void operation() {
System.out.println("具体产品A的操作");
}
}
// 具体产品B
class ConcreteProductB implements Product {
public void operation() {
System.out.println("具体产品B的操作");
}
}
// 工厂
interface Factory {
Product createProduct();
}
// 具体工厂A
class ConcreteFactoryA implements Factory {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂B
class ConcreteFactoryB implements Factory {
public Product createProduct() {
return new ConcreteProductB();
}
}
// 使用工厂模式
public class FactoryPattern {
public static void main(String[] args) {
Factory factoryA = new ConcreteFactoryA();
Product productA = factoryA.createProduct();
productA.operation();
Factory factoryB = new ConcreteFactoryB();
Product productB = factoryB.createProduct();
productB.operation();
}
}
- Springboot 自动注入
首先,定义一个抽象产品接口:
public interface Product {
void operation();
}
然后,创建具体产品类实现该接口:
@Component
public class ConcreteProductA implements Product {
public void operation() {
System.out.println("具体产品A的操作");
}
}
@Component
public class ConcreteProductB implements Product {
public void operation() {
System.out.println("具体产品B的操作");
}
}
接下来,创建一个简单工厂类,利用Spring的自动注入特性来获取具体产品实例:
@Component
public class SimpleFactory {
private final Map<String, Product> productMap;
@Autowired
public SimpleFactory(List<Product> products) {
productMap = new HashMap<>();
for (Product product : products) {
productMap.put(product.getClass().getSimpleName(), product);
}
}
public Product createProduct(String type) {
Product product = productMap.get(type);
if (product == null) {
throw new IllegalArgumentException("无效的产品类型");
}
return product;
}
}
在上述代码中,通过构造函数注入了所有实现了Product接口的具体产品类,并将它们存储在一个Map中,以产品类名作为键。然后,通过createProduct方法根据传入的产品类型获取相应的产品实例。也可以直接通过@Autowired Map<String, Product> products 方式实现,上面实现等同于如下方式
@Component
public class SimpleFactory {
@Autowired
Map<String, Product> productMap;
public Product createProduct(String type) {
Product product = productMap.get(type);
if (product == null) {
throw new IllegalArgumentException("无效的产品类型");
}
return product;
}
}
最后,在需要使用产品的地方,可以通过自动注入SimpleFactory来创建产品:
@RestController
public class ProductController {
private final SimpleFactory factory;
@Autowired
public ProductController(SimpleFactory factory) {
this.factory = factory;
}
@GetMapping("/product/{type}")
public String getProduct(@PathVariable String type) {
Product product = factory.createProduct(type);
product.operation();
return "产品操作完成";
}
}
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
解释
抽象工厂模式是工厂方法模式的升级版本,提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体的类。抽象工厂模式可以创建一组相关的产品对象,这些产品对象通常属于同一个产品族。
优点
使用抽象工厂时,我们只要知道它的工厂方法就可以直接产生一个产品对象,无须关心它的实现类。
- 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是抽象,它不关心对象是如何创建出来,这由谁负责呢?工厂类,只要知道工厂类是谁,我就能创建出一个需要的对象,省时省力,优秀设计就应该如此。
产品族内的约束为非公开状态。例如生产男女比例的问题上,猜想女娲娘娘肯定有自己的打算,不能让女盛男衰,否则女性的优点不就体现不出来了吗?那在抽象工厂模式,就应该有这样的一个约束:每生产1个女性,就同时生产出1.2个男性,这样的生产过程对调用工厂类的高层模块来说是透明的,它不需要知道这个约束,我就是要一个黄色女性产品就可以了,具体的产品族内的约束是在工厂内实现的。
实现
// 1.为形状创建一个接口。
public interface Shape {
void draw();
}
// 2.创建实现接口的实体类。
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
// 3.为颜色创建一个接口。
public interface Color {
void fill();
}
// 4.创建实现接口的实体类。
public class Red implements Color {
@Override
public void fill() {
System.out.println("Inside Red::fill() method.");
}
}
public class Green implements Color {
@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}
public class Green implements Color {
@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}
// 5.为 Color 和 Shape 对象创建抽象类来获取工厂。
public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape);
}
// 6.创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。
public class ShapeFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
@Override
public Color getColor(String color) {
return null;
}
}
public class ColorFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType){
return null;
}
@Override
public Color getColor(String color) {
if(color == null){
return null;
}
if(color.equalsIgnoreCase("RED")){
return new Red();
} else if(color.equalsIgnoreCase("GREEN")){
return new Green();
} else if(color.equalsIgnoreCase("BLUE")){
return new Blue();
}
return null;
}
}
// 7.创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂。
public class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
} else if(choice.equalsIgnoreCase("COLOR")){
return new ColorFactory();
}
return null;
}
}
// 8.使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。
public class AbstractFactoryPatternDemo {
public static void main(String[] args) {
//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
//获取形状为 Circle 的对象
Shape shape1 = shapeFactory.getShape("CIRCLE");
//调用 Circle 的 draw 方法
shape1.draw();
//获取形状为 Rectangle 的对象
Shape shape2 = shapeFactory.getShape("RECTANGLE");
//调用 Rectangle 的 draw 方法
shape2.draw();
//获取形状为 Square 的对象
Shape shape3 = shapeFactory.getShape("SQUARE");
//调用 Square 的 draw 方法
shape3.draw();
//获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
//获取颜色为 Red 的对象
Color color1 = colorFactory.getColor("RED");
//调用 Red 的 fill 方法
color1.fill();
//获取颜色为 Green 的对象
Color color2 = colorFactory.getColor("GREEN");
//调用 Green 的 fill 方法
color2.fill();
//获取颜色为 Blue 的对象
Color color3 = colorFactory.getColor("BLUE");
//调用 Blue 的 fill 方法
color3.fill();
}
}
模板方法模式
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
解释
模板方法模式确实非常简单,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。其中,AbstractClass叫做抽象模板,它的方法分为两类
- 基本方法。基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
- 模板方法。可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
优点
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
- 行为由父类控制,子类实现 (开闭原则)
实现
在Java中,可以使用抽象类和具体方法来实现模板方法模式。抽象类定义了算法的骨架,包含了一些具体方法和抽象方法。具体方法在抽象类中已经实现,而抽象方法则需要在子类中进行具体实现。
abstract class AbstractClass {
// 模板方法,定义了算法的骨架
public final void templateMethod() {
// 调用具体方法
concreteMethod1();
// 调用抽象方法,由子类实现
abstractMethod1();
// 调用钩子方法,由子类决定是否重写
hookMethod();
}
// 具体方法,已经实现
public void concreteMethod1() {
System.out.println("This is concrete method 1.");
}
// 抽象方法,由子类实现
public abstract void abstractMethod1();
// 钩子方法,由子类决定是否重写
public void hookMethod() {
// 默认实现为空
}
}
class ConcreteClass extends AbstractClass {
// 实现抽象方法
public void abstractMethod1() {
System.out.println("This is abstract method 1 in ConcreteClass.");
}
// 重写钩子方法
public void hookMethod() {
System.out.println("This is hook method in ConcreteClass.");
}
}
public class Main {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
解释
建造者模式是一种创建型设计模式,用于创建复杂对象。它将对象的构建过程拆分成多个步骤,并且可以按需选择步骤的方式来构建对象。 建造者模式的主要目的是将对象的构建过程与表示分离,以便相同的构建过程可以创建不同的表示。它提供了一种灵活的方式来构建复杂对象,同时隐藏了构建过程的细节。
优点
- 将复杂对象的构建过程与表示分离,使得构建过程更加灵活。
- 可以隐藏构建细节,使客户端代码与具体构建过程解耦。
- 可以创建不同表示的对象,只需改变具体建造者即可。
实现
建造者模式通常包含以下几个角色:
- 产品(Product):要构建的复杂对象。
- 抽象建造者(Builder):定义了构建产品的抽象方法和步骤。
- 具体建造者(Concrete Builder):实现了抽象建造者接口,负责实际构建产品的具体步骤。
- 指挥者(Director):负责按照特定顺序调用建造者的方法来构建产品。
使用建造者模式的主要步骤如下:
- 定义产品类,确定产品的属性和方法。
- 创建抽象建造者接口,定义构建产品的抽象方法。
- 创建具体建造者类,实现抽象建造者接口,实现构建产品的具体步骤。
- 创建指挥者类,负责按照特定顺序调用建造者的方法来构建产品。
- 在客户端中,通过指挥者来构建产品,客户端无需知道具体的构建细节。
- 通用实现方式
// 产品类
class Product {
private String part1;
private String part2;
private String part3;
public void setPart1(String part1) {
this.part1 = part1;
}
public void setPart2(String part2) {
this.part2 = part2;
}
public void setPart3(String part3) {
this.part3 = part3;
}
public void show() {
System.out.println("Part 1: " + part1);
System.out.println("Part 2: " + part2);
System.out.println("Part 3: " + part3);
}
}
// 抽象建造者类
abstract class Builder {
protected Product product = new Product();
public abstract void buildPart1();
public abstract void buildPart2();
public abstract void buildPart3();
public Product getResult() {
return product;
}
}
// 具体建造者类
class ConcreteBuilder extends Builder {
public void buildPart1() {
product.setPart1("Part 1");
}
public void buildPart2() {
product.setPart2("Part 2");
}
public void buildPart3() {
product.setPart3("Part 3");
}
}
// 指挥者类
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
product.show();
}
}
- Lombok
@Builder
通过调用 Person.builder()
方法,可以获得一个 PersonBuilder
对象,然后可以使用链式调用来设置属性的值。最后,通过调用 build()
方法来构建 Person
对象。
@Builder
@Getter
public class Person {
@NonNull
private String firstName;
@NonNull
private String lastName;
private int age;
private String address;
public static void main(String[] args) {
Person person = Person.builder()
.firstName("John")
.lastName("Doe")
.age(30)
.address("123 Main St")
.build();
System.out.println(person.getFirstName());
System.out.println(person.getLastName());
System.out.println(person.getAge());
System.out.println(person.getAddress());
}
}
代理模式
也叫委托模式,为其他对象提供一种代理以控制对这个对象的访问。
解释
代理模式是一种结构型设计模式,它提供了一个代理对象来控制对另一个对象的访问。代理对象充当了客户端和被代理对象之间的中介,通过代理对象可以间接访问被代理对象。
代理模式的主要目的是为其他对象提供一个代理或占位符,以控制对这个对象的访问。它可以增加额外的功能,或者在访问对象之前和之后执行一些操作。
优点
- 职责清晰。真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
- 高扩展性。具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
- 智能化。比如Struts是如何把表单元素映射到对象上的。
实现
最经典的实现是Spring AOP
动态代理。
代理模式通常包含以下几个角色:
- 抽象主题(Subject):抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。它定义了被代理对象和代理对象的共同接口,这样代理对象可以替代被代理对象进行访问。
- 具体主题(Real Subject):也叫做真实主题、被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。实现了抽象主题接口,是被代理的真正对象,代理对象通过调用真实主题的方法来实现对被代理对象的访问。
代理主题(Proxy):它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作(可以在访问真实主题之前或之后执行一些额外的操作)。它实现了抽象主题接口,同时持有一个对真实主题对象的引用。
- 通用代理
// 抽象主题接口
interface Subject {
void doSomething();
}
// 真实主题类
class RealSubject implements Subject {
public void doSomething() {
System.out.println("RealSubject doSomething");
}
}
// 代理类
class Proxy implements Subject {
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
public void doSomething() {
System.out.println("Proxy doSomething");
// 在调用真实主题前后可以执行一些额外的操作
realSubject.doSomething();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.doSomething();
}
}
- 动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 抽象主题接口
interface Subject {
void doSomething();
}
// 真实主题类
class RealSubject implements Subject {
public void doSomething() {
System.out.println("RealSubject doSomething");
}
}
// 代理处理器类
class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Proxy doSomething");
// 在调用真实主题前后可以执行一些额外的操作
Object result = method.invoke(target, args);
return result;
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
InvocationHandler handler = new ProxyHandler(realSubject);
// 创建动态代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler
);
proxy.doSomething();
}
}
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
解释
需要创建一个原型对象,并确保该对象实现了Cloneable
接口。然后,我们可以使用clone()
方法来创建新的对象,该方法会将原型对象的状态复制到新创建的对象中。
优点
- 性能优良。原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
- 逃避构造函数的约束。这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
注意
- 构造函数不会被执行。对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,
Object
类的clone
方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块。 - 浅拷贝。
Object
类的clone
方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,也就是说clone
是一种浅拷贝。
实现
package prototype;
// 创建一个可复制的原型对象
class Prototype implements Cloneable {
private String name;
public Prototype(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
// 使用原型对象创建新对象
public class PrototypePattern {
public static void main(String[] args) {
Prototype prototype = new Prototype("原型对象");
try {
// 使用 clone() 方法创建新对象
Prototype clone = prototype.clone();
clone.setName("克隆对象");
System.out.println("原型对象名称:" + prototype.getName());
System.out.println("克隆对象名称:" + clone.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
中介者模式
也叫做调停者模式,用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
解释
在中介者模式中,多个对象之间的交互不再直接发生,而是通过中介者对象进行协调。这样,对象之间的耦合度就降低了,它们只需要与中介者对象进行通信,而不需要了解其他对象的细节。 中介者模式的核心是中介者对象,它负责协调对象之间的交互。对象之间的通信通常通过中介者对象的方法来进行,而不是直接相互调用方法。
中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单
优点
中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
缺点
中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
实现
package mediator;
// 中介者接口
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
// 具体中介者实现
class ConcreteMediator implements Mediator {
private Colleague colleague1;
private Colleague colleague2;
public void setColleague1(Colleague colleague1) {
this.colleague1 = colleague1;
}
public void setColleague2(Colleague colleague2) {
this.colleague2 = colleague2;
}
@Override
public void sendMessage(String message, Colleague colleague) {
if (colleague.equals(colleague1)) {
System.out.println("ConcreteColleague2 准备通过中介接收 ConcreteColleague1发送的消息");
colleague2.receiveMessage(message);
} else if (colleague.equals(colleague2)) {
System.out.println("ConcreteColleague1 准备通过中介接收 ConcreteColleague2发送的消息");
colleague1.receiveMessage(message);
}
}
}
// 同事类接口
interface Colleague {
void sendMessage(String message);
void receiveMessage(String message);
}
// 具体同事类实现
class ConcreteColleague1 implements Colleague {
private Mediator mediator;
public ConcreteColleague1(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void sendMessage(String message) {
System.out.println(this.getClass().getSimpleName() + " 通过中介开始发送消息:" + message);
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println("ConcreteColleague1 接收到消息:" + message);
}
}
// 具体同事类实现
class ConcreteColleague2 implements Colleague {
private Mediator mediator;
public ConcreteColleague2(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void sendMessage(String message) {
System.out.println(this.getClass().getSimpleName() + " 通过中介开始发送消息:" + message);
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println("ConcreteColleague2 接收到消息:" + message);
}
}
// 测试代码
public class MediatorPattern {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.sendMessage("Hello from Colleague1");
colleague2.sendMessage("Hi from Colleague2");
}
}
命令模式
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
解释
命令模式是一种行为型设计模式,它将请求封装为一个对象,从而使请求的发送者和接收者解耦。在命令模式中,请求被封装为一个命令对象,该对象包含了执行该请求所需的方法和参数。这样,请求的发送者只需要调用命令对象的方法,而无需知道具体的接收者或执行细节。
通过命令模式,我们可以将请求的发送者和接收者解耦,使得它们可以独立地变化。请求的发送者只需要与命令对象进行交互,而无需知道具体的接收者或执行细节。这种解耦可以带来更好的灵活性和可扩展性。
优点
- 类间解耦。调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
- 可扩展性。Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
- 命令模式结合其他模式会更优秀。命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
缺点
命令模式也是有缺点的,请看Command的子类:如果有N个命令,问题就出来了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要在项目中慎重考虑使用。
实现
package command;
// 命令接口
interface Command {
void execute();
}
// 具体命令类
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
// 接收者类
class Receiver {
public void action() {
System.out.println("接收者执行操作");
}
}
// 请求者类
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
// 测试代码
public class CommandPattern {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand();
}
}
责任链模式
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
解释
职责链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许将请求沿着处理链进行传递,直到有一个处理者能够处理该请求为止。在职责链模式中,每个处理者都有一个对下一个处理者的引用,形成一个链条。 职责链模式的核心思想是解耦请求发送者和接收者,使得多个对象都有机会处理请求。当一个请求被发送时,它会沿着处理链依次传递,直到有一个处理者能够处理该请求。这样,请求的发送者无需知道具体的处理者,而处理者也无需知道请求的发送者,它们只需要知道自己的下一个处理者即可。
优点
请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理),两者解耦,提高系统的灵活性。
缺点
- 性能问题。每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。
- 调试不方便。特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
注意
链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
实现
package chain;
// 抽象处理者
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(int request);
}
// 具体处理者A
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 0 && request < 10) {
System.out.println("ConcreteHandlerA 处理请求:" + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 具体处理者B
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 10 && request < 20) {
System.out.println("ConcreteHandlerB 处理请求:" + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 具体处理者C
class ConcreteHandlerC extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 20 && request < 30) {
System.out.println("ConcreteHandlerC 处理请求:" + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 测试代码
public class Chain {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
Handler handlerC = new ConcreteHandlerC();
handlerA.setNextHandler(handlerB);
handlerB.setNextHandler(handlerC);
handlerA.handleRequest(5);
handlerA.handleRequest(15);
handlerA.handleRequest(25);
}
}
装饰模式
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活
解释
装饰器模式是一种结构型设计模式,它允许在不改变对象接口的情况下,动态地向对象添加新的行为。装饰器模式通过创建一个包装对象来实现这一目的,该包装对象包含了原始对象,并在其上添加了额外的功能。 在装饰器模式中,有一个抽象基类或接口,定义了原始对象和装饰器对象都要实现的方法。然后,有一个具体的原始对象类,实现了基类或接口的方法。接下来,有一个装饰器类,它也实现了基类或接口,并且包含一个指向原始对象的引用。装饰器类可以在调用原始对象的方法之前或之后,添加额外的功能。
优点
- 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
- 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。
- 装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。
使用场景
- 需要扩展一个类的功能,或给一个类增加附加功能。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
实现
package decorator;
// 抽象组件接口
interface Component {
void operation();
}
// 具体组件类
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("执行具体组件的操作");
}
}
// 抽象装饰者类
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰者类A
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("执行具体装饰者A的操作");
}
}
// 具体装饰者类B
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("执行具体装饰者B的操作");
}
}
// 测试代码
public class DecoratorPattern {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(component);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
}
}
策略模式
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
解释
策略模式使用的是面向对象的继承和多态机制。
策略模式通过定义一系列算法,并将每个算法封装在独立的类中,使得它们可以互相替换。这样,客户端可以根据需要选择不同的算法,而不必更改其代码。
优点
- 算法可以自由切换。这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。
避免使用多重条件判断。如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
- 扩展性良好。这甚至都不用说是它的优点,因为它太明显了。在现有的系统中增加一个策略太容易了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符合了OCP原则(开闭原则)。
缺点
- 策略类数量增多。每一个策略都是一个类,复用的可能性很小,类数量增多。
所有的策略类都需要对外暴露。上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则 是相违背的,我只是想使用了一个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?这是原装策略模式的一个缺点,幸运的是,我们可以使用其他模式来修正这个缺陷,如工厂方法模式、代理模式或享元模式。
使用场景
- 多个类只有在算法或行为上稍有不同的场景。
- 算法需要自由切换的场景。
- 需要屏蔽算法规则的场景。
实现
package strategy;
// 策略接口
interface SortingStrategy {
void sort(int[] numbers);
}
// 具体策略类 - 冒泡排序
class BubbleSortStrategy implements SortingStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Using Bubble Sort");
// 冒泡排序算法实现
}
}
// 具体策略类 - 快速排序
class QuickSortStrategy implements SortingStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Using Quick Sort");
// 快速排序算法实现
}
}
// 上下文类
class SortContext {
private SortingStrategy strategy;
public SortContext(SortingStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}
public void sortNumbers(int[] numbers) {
strategy.sort(numbers);
}
}
// 客户端代码
public class Strategy {
public static void main(String[] args) {
int[] numbers = {5, 2, 8, 9, 1};
SortContext context = new SortContext(new BubbleSortStrategy());
context.sortNumbers(numbers);
context.setStrategy(new QuickSortStrategy());
context.sortNumbers(numbers);
}
}
适配器模式
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
解释
适配器模式又叫做变压器模式,也叫做包装模式(Wrapper)。
适配器模式涉及三个主要角色:目标接口(Target Interface)、适配器(Adapter)和被适配者(Adaptee)。目标接口定义了客户端所期望的方法。适配器类实现了目标接口,并包含一个对被适配者的引用。被适配者是已经存在的类,它的接口与目标接口不兼容。 适配器模式通过在适配器类中实现目标接口的方法,并在方法内部调用被适配者的方法,将客户端的请求转发给被适配者。这样,客户端可以通过适配器类与被适配者进行交互,而不需要直接与被适配者进行通信。
优点
- 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
- 增加了类的透明性。
- 提高了类的复用度
- 灵活性非常好
使用场景
你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式。
注意
适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题。
适配器模式是一个补偿模式,或者说是一个“补救”模式,通常用来解决接口不相容的问题。
实现
- 类适配器的通用类图
package adapter;
// 目标接口
interface Target {
void request();
}
// 被适配者类
class Adaptee {
public void specificRequest() {
System.out.println("被适配者的specificRequest正在被调用");
}
}
// 适配器类
class Adapter extends Adaptee implements Target {
@Override
public void request() {
System.out.println("适配者内部准备调用被适配者的specificRequest");
super.specificRequest();
System.out.println("开始转换。。。经过一系列转换,变成目标实例request");
}
}
// 客户端代码
public class AdapterPattern {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
- 对象适配器的通用类图
// 目标接口
public interface Target {
void request();
}
// 被适配者类
public class Adaptee {
public void specificRequest() {
System.out.println("Specific request");
}
}
// 适配器类
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request();
}
}
迭代器模式
它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
注意
迭代器模式(Iterator Pattern)目前已经是一个没落的模式,基本上没人会单独写一个迭代器,除非是产品性质的开发。
实现
package iterator;
// 迭代器接口
interface Iterator {
boolean hasNext();
String next();
}
// 集合接口
interface Collection {
Iterator createIterator();
}
// 具体迭代器类
class ConcreteIterator implements Iterator {
private String[] collection;
private int position = 0;
public ConcreteIterator(String[] collection) {
this.collection = collection;
}
@Override
public boolean hasNext() {
return position < collection.length;
}
@Override
public String next() {
if (hasNext()) {
return collection[position++];
}
return null;
}
}
// 具体集合类
class ConcreteCollection implements Collection {
private String[] collection;
public ConcreteCollection(String[] collection) {
this.collection = collection;
}
@Override
public Iterator createIterator() {
return new ConcreteIterator(collection);
}
}
// 客户端代码
public class IteratorPattern {
public static void main(String[] args) {
String[] collection = {"Item 1", "Item 2", "Item 3", "Item 4", "Item 5"};
Collection concreteCollection = new ConcreteCollection(collection);
Iterator iterator = concreteCollection.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
组合模式
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
解释
组合模式(Composite Pattern)也叫合成模式,有时又叫做部分-整体模式(Part-Whole),主要是用来描述部分与整体的关系。
在组合模式中,有两种主要的对象类型:叶节点和组合节点。叶节点表示树形结构中的最底层对象,它们没有子节点。而组合节点则可以包含子节点,形成更大的组合对象。
组合模式通过定义共同的接口,使得客户端可以透明地操作单个对象和组合对象。客户端可以像对待单个对象一样对待组合对象,而不需要关心对象的具体类型。
优点
- 高层模块调用简单
- 节点自由增加
使用场景
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
实现
- 透明模式。把用来组合使用的方法放到抽象类中。
// 抽象组件接口 也可以是抽象类,书上使用抽象类
interface Component {
void operation();
void add(Component component);
void remove(Component component);
}
// 叶节点类
class Leaf implements Component {
@Override
public void operation() {
System.out.println("执行叶节点操作");
}
@Override
public void add(Component component) {
throw new UnsupportedOperationException();
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
// 组合节点类
class Composite implements Component {
private List<Component> children = new ArrayList<>();
@Override
public void operation() {
System.out.println("执行组合节点操作");
for (Component component : children) {
component.operation();
}
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建组合对象
Composite composite = new Composite();
// 创建叶节点对象
Leaf leaf1 = new Leaf();
Leaf leaf2 = new Leaf();
// 将叶节点添加到组合对象中
composite.add(leaf1);
composite.add(leaf2);
// 执行组合对象操作
composite.operation();
}
}
- 安全模式
package composite;
import java.util.ArrayList;
import java.util.List;
// 抽象组件接口 也可以是抽象类,书上使用抽象类
interface Component {
void operation();
}
// 叶节点类
class Leaf implements Component {
@Override
public void operation() {
System.out.println("执行叶节点操作");
}
}
// 组合节点类
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
System.out.println("执行组合节点操作");
for (Component component : children) {
component.operation();
}
}
}
// 客户端代码
public class CompositePattern {
public static void main(String[] args) {
// 创建组合对象
Composite root = new Composite();
Composite composite1 = new Composite();
Composite composite2 = new Composite();
// 创建叶节点对象
Leaf leaf1 = new Leaf();
Leaf leaf2 = new Leaf();
Leaf leaf3 = new Leaf();
Leaf leaf4 = new Leaf();
// 将叶节点添加到组合对象中
root.add(composite1);
root.add(composite2);
composite1.add(leaf1);
composite1.add(leaf2);
composite2.add(leaf3);
composite2.add(leaf4);
// 执行组合对象操作
root.operation();
}
}
观察者模式
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
解释
在观察者模式中,有两个核心角色:主题(Subject)和观察者(Observer)。主题是被观察的对象,它维护了一个观察者列表,并提供了添加、删除和通知观察者的方法。观察者是接收主题通知的对象,它定义了一个更新的方法,用于在主题状态发生变化时更新自身。
观察者模式的实现通常涉及以下几个步骤:
- 定义主题接口(Subject):主题接口中包含了添加、删除和通知观察者的方法。
- 定义观察者接口(Observer):观察者接口中定义了更新的方法,用于在主题状态发生变化时更新自身。
- 实现主题类(具体主题):具体主题类实现了主题接口,维护了观察者列表,并在状态发生变化时通知观察者。
- 实现观察者类(具体观察者):具体观察者类实现了观察者接口,定义了更新的方法,用于接收主题的通知并更新自身状态。
观察者模式的优点包括了松耦合、可扩展性和易维护性。它可以帮助我们实现对象之间的一种动态关系,以应对复杂的业务需求。
优点
- 观察者和被观察者之间是抽象耦合
- 建立一套触发机制
使用场景
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列的处理机制。
缺点
观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
多级触发时的效率更是让人担忧,大家在设计时注意考虑。
实现
package observer;
import java.util.ArrayList;
import java.util.List;
// 主题接口
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update();
}
// 具体主题类
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 具体观察者类
class ConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("接收到主题的通知,进行更新操作");
}
}
// 客户端代码
public class SimpleObserver {
public static void main(String[] args) {
// 创建主题和观察者对象
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer = new ConcreteObserver();
// 将观察者添加到主题中
subject.addObserver(observer);
// 主题状态发生变化,通知观察者
subject.notifyObservers();
}
}
- 使用JDK的Observer和Observable
package observer.java;
import java.util.Observable;
import java.util.Observer;
// 主题类
class Subject extends Observable {
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
super.setChanged();
super.notifyObservers("正在密谋。。。。");
}
}
// 观察者类
class ConcreteObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println( "接收到主题的通知:"+o.getClass().getSimpleName()+arg);
}
}
// 客户端代码
public class JavaObserver {
public static void main(String[] args) {
// 创建主题和观察者对象
Subject subject = new Subject();
ConcreteObserver observer = new ConcreteObserver();
// 将观察者添加到主题中
subject.addObserver(observer);
// 主题状态发生变化,通知观察者
subject.setState(1);
}
}
门面模式
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
解释
门面模式(Facade Pattern)也叫做外观模式,是一种结构型设计模式,它提供了一个统一的接口,用于访问子系统中的一组接口。门面模式隐藏了子系统的复杂性,使得客户端可以通过简单的接口与子系统进行交互,而无需了解子系统的内部实现细节。
门面模式的核心思想是将复杂的子系统封装在一个门面类中,客户端只需要与门面类进行交互,而无需直接与子系统的各个组件进行交互。门面类负责将客户端的请求转发给适当的子系统组件,并将结果返回给客户端。
门面模式的优点包括了简化客户端与子系统之间的交互、降低了客户端与子系统之间的耦合度、提高了代码的可维护性和可扩展性等。
优点
- 减少系统的相互依赖
- 提高了灵活性
- 提高安全性
使用场景
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立--外界对子系统的访问只要黑箱操作即可
- 预防低水平人员带来的风险扩散
实现
// 子系统组件A
class SubsystemA {
public void operationA() {
System.out.println("SubsystemA: operationA");
}
}
// 子系统组件B
class SubsystemB {
public void operationB() {
System.out.println("SubsystemB: operationB");
}
}
// 门面类
class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
}
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
}
}
// 客户端代码
public class FacadePattern {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation();
}
}
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
解释
备忘录模式的核心思想是将对象的状态保存在备忘录对象中,以便在需要时可以恢复到之前的状态。通过将状态的保存和恢复工作委托给备忘录对象,可以避免直接暴露对象的内部状态给其他对象。
使用场景
- 需要保存和恢复数据的相关状态场景。
- 提供一个可回滚(rollback)的操作。
- 需要监控的副本场景中。
- 数据库连接的事务管理就是用的备忘录模式
实现
package memento;
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 发起人类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento memento) {
state = memento.getState();
}
}
// 管理者类
class Caretaker {
private Memento memento;
public void setMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
// 客户端代码
public class MementoPattern {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 设置发起人的状态
originator.setState("State 1");
System.out.println("Current state: " + originator.getState());
// 创建备忘录并保存发起人的状态
caretaker.setMemento(originator.createMemento());
// 修改发起人的状态
originator.setState("State 2");
System.out.println("Current state: " + originator.getState());
// 恢复发起人的状态
originator.restoreMemento(caretaker.getMemento());
System.out.println("Restored state: " + originator.getState());
}
}
- clone方式,实现Cloneable,重写clone()。原型模式中有介绍
访问者模式
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
优点
- 符合单一职责原则
- 优秀的扩展性
- 灵活性非常高
缺点
- 具体元素对访问者公布细节
- 具体元素变更比较困难
- 违背了依赖倒置转原则
使用场景
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
实现
package visitor;
import java.util.ArrayList;
import java.util.List;
// 访问者接口
interface Visitor {
void visit(ElementA elementA);
void visit(ElementB elementB);
}
// 具体访问者实现
class ConcreteVisitor implements Visitor {
@Override
public void visit(ElementA elementA) {
System.out.println("访问者对元素A进行操作");
}
@Override
public void visit(ElementB elementB) {
System.out.println("访问者对元素B进行操作");
}
}
// 元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体元素实现
class ElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class ElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 对象结构
class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void addElement(Element element) {
elements.add(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
// 客户端代码
public class VisitorPattern {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(new ElementA());
objectStructure.addElement(new ElementB());
Visitor visitor = new ConcreteVisitor();
objectStructure.accept(visitor);
}
}
状态模式
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
解释
状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为。状态模式通过将对象的状态封装成不同的类,并将对象的行为委托给这些状态类之一来实现。当对象的状态发生变化时,它会改变委托的状态类,从而改变它的行为。
状态模式的核心思想是将不同的状态封装成独立的类,这些状态类都实现了相同的接口或继承了相同的抽象类。在使用状态模式时,可以通过改变对象的状态类来改变对象的行为,而不需要在对象的方法中使用大量的条件判断语句。
优点
- 结构清晰。避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提高系统的可维护性。
- 遵循设计原则。很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
- 封装性非常好。
缺点
子类会太多,也就是类膨胀 。
解决方案:
- 在数据库中建立一个状态表,然后根据状态执行相应的操作。
使用场景
- 行为随状态改变而改变的场景。
- 条件、分支判断语句的替代者。
注意
- 把状态对象声明为静态常量,有几个状态对象就声明几个静态常量。
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式。
实现
package state;
//抽象状态角色
abstract class State {
//定义一个环境角色,提供子类访问
protected Context context;
//设置环境角色
public void setContext(Context _context) {
this.context = _context;
}
//行为1
public abstract void handle1();
//行为2
public abstract void handle2();
}
//具体状态角色
class ConcreteState1 extends State {
@Override
public void handle1() {
//本状态下必须处理的逻辑
System.out.println(this.getClass().getSimpleName()+"必须做的事情");
}
@Override
public void handle2() {
//设置当前状态为stat2
super.context.setCurrentState(Context.STATE2);
//过渡到state2状态,由Context实现
super.context.handle2();
}
}
class ConcreteState2 extends State {
@Override
public void handle1() {
//设置当前状态为state1
super.context.setCurrentState(Context.STATE1);
//过渡到state1状态,由Context实现
super.context.handle1();
}
@Override
public void handle2() {
//本状态下必须处理的逻辑
System.out.println(this.getClass().getSimpleName()+"必须做的事情");
}
}
class Context {
//定义状态
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
//当前状态
private State currentState;
//获得当前状态
public State getCurrentState() {
return currentState;
}
//设置当前状态
public void setCurrentState(State currentState) {
this.currentState = currentState;
//切换状态 注意这个地方的 this
this.currentState.setContext(this);
}
//行为委托
public void handle1() {
this.currentState.handle1();
}
public void handle2() {
this.currentState.handle2();
}
}
//具体环境角色
public class StatePattern {
public static void main(String[] args) {
//定义环境角色
Context context = new Context();
//初始化状态
context.setCurrentState(new ConcreteState1());
//行为执行
context.handle1();
context.handle2();
context.handle1();
context.handle2();
}
}
// 运行结果
// ConcreteState1必须做的事情
// ConcreteState2必须做的事情
// ConcreteState2必须做的事情
// ConcreteState1必须做的事情
解释器模式
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释
解释器模式是一种行为设计模式,它用于定义一个语言的语法,并解析该语言中的表达式。该模式将每个语法规则表示为一个类,并定义了解释方法来解析表达式。通过使用解释器模式,可以轻松地扩展和修改语言的语法,同时使解析过程更加灵活和可定制。
在解释器模式中,通常有两种类型的类:终结符表达式和非终结符表达式。终结符表达式代表语言中的基本元素,而非终结符表达式由多个终结符表达式和其他非终结符表达式组成。
优点
解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
缺点
- 解释器模式会引起类膨胀
解释器模式采用递归调用方法。每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,直到最小的语法单元。
- 效率问题
使用场景
- 重复发生的问题可以使用解释器模式
- 一个简单语法需要解释的场景
实现
package interpreter;
// 抽象表达式类
interface Expression {
int interpret();
}
// 终结符表达式类
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
// 非终结符表达式类 加法
class AddExpression implements Expression {
private Expression leftExpression;
private Expression rightExpression;
public AddExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
@Override
public int interpret() {
return leftExpression.interpret() + rightExpression.interpret();
}
}
// 非终结符表达式类 减法
class SubtractExpression implements Expression {
private Expression leftExpression;
private Expression rightExpression;
public SubtractExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
@Override
public int interpret() {
return leftExpression.interpret() - rightExpression.interpret();
}
}
public class Interpreter {
public static void main(String[] args) {
// 构建解释器表达式:(5 + 2) - (4 - 1)
Expression expression = new SubtractExpression(
new AddExpression(
new NumberExpression(5),
new NumberExpression(2)
),
new SubtractExpression(
new NumberExpression(4),
new NumberExpression(1)
)
);
// 解析并计算表达式的结果
int result = expression.interpret();
System.out.println("解释器模式计算结果:" + result);
}
}
享元模式
使用共享对象可有效地支持大量的细粒度的对象。
优点
可以大大减少应用程序创建的对象,降低程序内存的占用,增强程序的性能
缺点
提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
使用场景
- 系统中存在大量的相似对象。
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
- 需要缓冲池的场景。
注意
关于线程安全问题,在需要的地方考虑一下线程安全,在大部分的场景下都不用考虑。我们在使用享元模式时,对
象池中的享元对象尽量多,多到足够满足业务为止。
实现
package flyweight;
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface Flyweight {
void operation();
}
// 具体享元类
class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation() {
System.out.println("具体享元对象操作,内部状态为:" + intrinsicState);
}
}
// 享元工厂类
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (flyweights.containsKey(key)) {
return flyweights.get(key);
} else {
Flyweight flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
return flyweight;
}
}
}
// 客户端代码
public class FlyweightPattern {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("A");
flyweight1.operation();
Flyweight flyweight2 = factory.getFlyweight("B");
flyweight2.operation();
Flyweight flyweight3 = factory.getFlyweight("A");
flyweight3.operation();
}
}
桥梁模式
将抽象和实现解耦,使得两者可以独立地变化。
优点
- 抽象和实现分离。这也是桥梁模式的主要特点,它完全是为了解决继承的缺点而提出的设计模式。在该模式下,实现可以不受抽象的约束,不用再绑定在一个固定的抽象层次上。
- 优秀的扩充能力
- 实现细节对客户透明
使用场景
- 不希望或不适用使用继承的场景
- 接口或抽象类不稳定的场景
- 重用性要求较高的场景
解释
桥梁模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其实现部分分离,以便它们可以独立地变化。该模式通过将抽象和实现解耦,使它们可以独立地进行扩展和修改。
桥梁模式的核心思想是将一个类的抽象部分和实现部分分离,使它们可以独立地变化。抽象部分通常由一个抽象类或接口表示,它定义了抽象类和实现类之间的接口。实现部分由具体实现类表示,它实现了抽象类或接口定义的方法。
通过桥梁模式,可以在运行时动态地选择和组合抽象类和实现类,从而实现不同的功能组合。这种灵活性使得桥梁模式特别适用于需要在运行时动态切换和组合不同实现的情况。
实现
package bridge;
//实现化角色 它没有任何特殊的地方,就是一个一般的接口,定义要实现的方法。
interface Implementor {
//基本方法
void doSomething();
void doAnything();
}
//具体实现化角色 定义了两个具体实现化角色——代表两个不同的业务逻辑
class ConcreteImplementor1 implements Implementor {
public void doSomething() {
//业务逻辑处理
System.out.println("3."+this.getClass().getSimpleName()+"正在做一些基础的事情");
}
public void doAnything() {
//业务逻辑处理
System.out.println("5."+this.getClass().getSimpleName()+"正在做一些自己想做的事情!");
}
}
class ConcreteImplementor2 implements Implementor {
public void doSomething() {
//业务逻辑处理
System.out.println(this.getClass().getSimpleName()+"正在做一些基础的事情");
}
public void doAnything() {
//业务逻辑处理
System.out.println(this.getClass().getSimpleName()+"正在做一些自己想做的事情!");
}
}
//抽象化角色 为什么要增加一个构造函数?答案是为了提醒子类,你必须做这项工 作,指定实现者,特别是已经明确了实现者,则尽量清晰明确地定义出来。
abstract class Abstraction {
//定义对实现化角色的引用
private Implementor imp;
//约束子类必须实现该构造函数
public Abstraction(Implementor _imp) {
this.imp = _imp;
}
//自身的行为和属性
public void request() {
System.out.println("2.抽象父类接收到了请求,正在通知子类干活");
this.imp.doSomething();
}
//获得实现化角色
public Implementor getImp() {
return imp;
}
}
//具体抽象化角色 如果我们的实现化角色有很多的子接口,然后是一堆的子实现。如果在构造函数中不传递一个尽量明确的实现者,代码就很不清晰。
class RefinedAbstraction extends Abstraction {
//覆写构造函数
public RefinedAbstraction(Implementor _imp) {
super(_imp);
}
//修正父类的行为
@Override
public void request() {
System.out.println("1.子类重写了父类方法");
/* * 业务处理... */
super.request();
System.out.println("4.子类通过父类提供的实现化角色额外做了一些事情");
super.getImp().doAnything();
}
}
//场景类
public class BridgePattern {
public static void main(String[] args) {
//定义一个实现化角色
Implementor imp = new ConcreteImplementor1();
//定义一个抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
//执行行文
abs.request();
}
}
设计模式PK
设计模式分类
创建型模式
该模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用new
运算符直接实例化对象,它的主要特点是将对象的创建与使用分离。
模式 | 功能 | 理解 |
---|---|---|
单例模式 | 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例 | 某个类能自行生成全局唯一实例。 |
工厂方法模式 | 定义一个用于创建(一类)产品的接口,由子类决定生产什么产品 | 由实现工厂接口的具体子类工厂决定生产什么产品,只能生产一类产品。 |
抽象工厂模式 | 提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品 | 工厂也抽象,产品也抽象,可以生产多类产品。 |
建造者(创建者)模式 | 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。可简单理解为对复杂对象进行分模块创建 | 将复杂的对象分解为多个简单的对象,然后分步骤构建。 |
原型模式 | 将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例 | 利用现有对象作为“原型”,通过克隆,创建相同或相似对象。 |
结构型模式
该模式关注类和对象的组合,即如何将类或对象按某种布局组成更大的结构。
模式 | 功能 | 理解 |
---|---|---|
适配器模式 | 将一个类的接口转换成另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,说白了就是接口转换 | 在开发者调用的接口和现有接口不一致时,增加一个"转换接口"。 |
桥梁(桥接)模式 | 将抽象部分与实现部分分离(这个说法不太直观,可以简单理解为从抽象类和接口两个维度来组合想要的对象),使它们都可以独立的变化 | 用抽象类和接口定义两个可变维度,分别对其进行不同的实现,从而组成多种不同对象。 |
组合模式 | 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性 | 定义一个接口,让树枝对象和树叶对象都实现该接口,并具有不同的实现,进而表现整体与部分的关系。多用于树形结构。 |
装饰模式 | 向现有的对象添加新的功能,同时又不改变其结构,即动态地给一个对象添加一些额外的职责 | 不改变现有对象的结构,动态地增加一些功能。继承装饰。 |
门面(外观)模式 | 隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,使得这一子系统更加容易使用 | 在复杂的系统上,提供一个对外的一致性接口(外观)。类似于统一入口。 |
享元模式 | 运用共享技术来有效地支持大量细粒度对象的复用 | 复用需要大量使用的、功能较为简单的对象。比如对象池。 |
代理模式 | 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性 | 用代理对象来控制对原有对象的访问权限。可以添加前置和后置处理。简单理解为有事找代理,代理再找真实的实现 |
行为型模式
该模式用于描述类或对象之间怎样通信、协作共同完成任务,以及怎样分配职责。
模式 | 功能 | 理解 |
---|---|---|
访问者模式 | 在不改变数据结构元素的前提下,为一个数据结构中的每个元素提供多种访问方式 | 定义一个对象,可以在不改变复杂对象的数据结构的前提下,访问数据结构中的各元素。 |
模板模式 | 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 | 父类定义固定的框架和公共部分,子类实现可变部分/步骤。 |
策略模式 | 定义一系列算法,将每个算法封装起来,使它们可以相互替换(同一个功能的不同实现) | 有多种独立的算法,供客户端替换使用。 |
状态模式 | 允许一个对象在其内部状态发生改变时改变其行为能力,常见场景为:一个对象的行为取决于它的状态 | 对象的行为依赖于其状态,将状态提取为状态对象,将对象之间的直接交互改成对象和状态对象之间的交互,降低对象之间的耦合度。 |
观察者模式 | 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为,常见的是:发布-订阅 | 一个对象变化了,要通知到别的对象,以便处理这种变化。 |
备忘录模式 | 在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它,撤销-恢复 | 可以保存和撤销对象的行为。 |
中介(调停)者模式 | 定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解 | 禁止多个对象之间相互"通信",交给中介者去进行,所有对象和交互者"通信"即可。有事都找中介,中介帮你干 |
迭代器模式 | 提供一种方法来顺序访问(遍历)聚合对象中的一系列数据,而不暴露聚合对象的内部表示 | 如何遍历对象。 |
解释器模式 | 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器 | 解释带有特定语法的句子。 |
命令模式 | 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开 | 将命令/请求封装为对象,在对象与对象之间传递信息,降低对象之间的耦合度。 |
责任(职责)链模式 | 将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链,直到请求被处理为止 | 多个请求处理者记录对下一个处理者的引用,降低请求发起者和多个处理者之间的耦合度。 |
创建型模式PK
工厂模式VS建造者模式
工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象。
- 意图不同。
在工厂方法模式里,我们关注的是一个产品整体,但在建造者模式中,一个具体产品的产生是依赖各个部件的产生以及装配顺序,它关注的是“由零件一步一步地组装出产品对象”。简单地说,工厂模式是一个对象创建的粗线条应用,建造者模式则是通过细线条勾勒出一个复杂对象,关注的是产品组成部分的创建过程。
- 产品的复杂度不同。
工厂方法模式创建的产品一般都是单一性质产品,而建造者模式创建的则是一个复合产品,它由各个部件复合而成,部件不同产品对象当然不同。这不是说工厂方法模式创建的对象简单,而是指它们的粒度大小不同。一般来说,工厂方法模式的对象粒度比较粗,建造者模式的产品对象粒度比较细。
在具体的应用中,我们该如何选择呢?
是用工厂方法模式来创建对象,还是用建造者模式来创建对象,这完全取决于我们在做系统设计时的意图,如果需要详细关注一个产品部件的生产、安装步骤,则选择建造者,否则选择工厂方法模式。
抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
结构类模式PK
代理模式VS装饰模式
代理模式控制对象访问权限;装饰模式用于向对象添加职责
装饰模式就是代理模式的一个特殊应用,两者的共同点是都具有相同的接口,不同点则是代理模式着重对代理过程的控制,而装饰模式则是对类的功能进行加强或减弱,它着重类的功能变化
- 相同点。
都是为被代理(被装饰)的类扩充新的功能。
- 不同点。
代理模式具有控制被代理类的访问等性质,而装饰模式紧紧是单纯的扩充被装饰的类。所以区别仅仅在是否对被代理/被装饰的类进行了控制而已。
代理模式使用到极致开发就是AOP
装饰模式VS适配器模式
装饰器模式是为了给现有对象增加功能,一般接口不变或接口增加;适配器模式是为了改变其接口,功能保持不变。
- 意图不同。
装饰模式的意图是加强对象的功能;而适配器模式关注的则是转化,它的主要意图是两个不同对象之间的转化。
- 施与对象不同。
装饰模式装饰的对象必须是自己的同宗,也就是相同的接口或父类只要在具有相同的属性和行为的情况下,才能比较行为是增加还是减弱;适配器模式则必须是两个不同的对象,因为它着重于转换,只有两个不同的对象才有转换的必要,如果是相同对象还转换什么!
- 场景不同。
装饰模式在任何时候都可以使用,只要是想增强类的功能,而适配器模式则是一个补救模式,一般出现在系统成熟或已经构建完毕的项目中,作为一个紧急处理手段采用。
- 扩展性不同。
装饰模式很容易扩展。但是适配器模式就不同了,它在两个不同对象之间架起了一座沟通的桥梁,建立容易,去掉就比较困难了,需要从系统整体考虑是否能够撤销。
行为类模式PK
命令模式VS策略模式
命令模式和策略模式的类图确实很相似,只是命令模式多了一个接收者(Receiver)角色。策略模式的意图是封装算法,它认为“算法”已经是一个完整的、不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户;而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立而不相互影响。
策略模式和命令模式相似,特别是命令模式退化时,比如无接收者(接收者非常简单或者接收者是一个Java的基础操作,无需专门编写一个接收者),在这种情况下,命令模式和策略模式的类图完全一样,代码实现也比较类似,但是两者还是有区别的。
- 关注点不同。
策略模式关注的是算法替换的问题,一个新的算法投产,旧算法退休,或者提供多种算法由调用者自己选择使用,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其可以自由切换。
命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它需要首先解决的,解耦的要求就是把请求的内容封装为一个一个的命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等。
- 职责不同。
策略模式中的具体算法是负责一个完整算法逻辑,它是不可再拆分的原子业务单元,一旦变更就是对算法整体的变更。而命令模式则不同,它关注命令的实现,也就是功能的实现。注意,如果在命令模式中需要指定接收者,则需要考虑接收者的变化和封装,也就是接收者的抽象化问题。
- 角色功能不同
策略模式适用于算法要求变换的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令多撤销的场景。
策略模式VS状态模式
在行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似。策略模式封装的是不同的算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。这两种模式虽然都有变换的行为,但是两者的目标却是不同的。
简单理解为状态模式关注对象所处的状态,封装了依赖于状态的行为;策略模式关注对象如何执行特定的任务,它封装的是算法。
- 环境角色的职责不同。
两者都有一个叫做Context环境角色的类,但是两者的区别很大,策略模式的环境角色只是一个委托作用,负责算法的替换;而状态模式的环境角色不仅仅是委托行为,它还具有登记状态变化的功能,与具体的状态类协作,共同完成状态切换行为随之切换的任务。
- 解决问题的重点不同。
策略模式旨在解决内部算法如何改变的问题,也就是将内部算法的改变对外界的影响降低到最小,它保证的是算法可以自由地切换;而状态模式旨在解决内在状态的改变而引起行为改变的问题,它的出发点是事物的状态,封装状态而暴露行为,一个对象的状态改变,从外界来看就好像是行为改变。
- 解决问题的方法不同。
策略模式只是确保算法可以自由切换,但是什么时候用什么算法它决定不了;而状态模式对外暴露的是行为,状态的变化一般是由环境角色和具体状态共同完成的,也就是说状态模式封装了状态的变化而暴露了不同的行为或行为结果。
- 应用场景不同。
策略模式是一系列平行的、可相互替换的算法封装后的结果,这就限定了它的应用场景:算法必须是平行的,否则策略模式就封装了一堆垃圾,产生了“坏味道”。状态模式则要求有一系列状态发生变化的场景,它要求的是有状态且有行为的场景,也就是一个对象必须具有二维(状态和行为)描述才能采用状态模式,如果只有状态而没有行为,则状态的变化就失去了意义。
- 复杂度不同。
通常策略模式比较简单,这里的简单指的是结构简单,扩展比较容易,而且代码也容易阅读。而状态模式则通常比较复杂,因为它要从两个角色看到一个对象状态和行为的改变,也就是说它封装的是变化,要知道变化是无穷尽的,因此相对来说状态模式通常都比较复杂,涉及面很多,虽然也很容易扩展,但是一般不会进行大规模的扩张和修正。
观察者模式VS责任链模式
- 链中的消息对象不同
责任链模式基本上不改变消息对象的结构,但是在触发链模式中是允许的,链中传递的对象可以自由变化,只要上下级节点对传递对象了解即可,它不要求链中的消息对象不变化,它只要求链中相邻两个节点的消息对象固定。
- 上下节点的关系不同
在责任链模式中,上下节点没有关系,都是接收同样的对象,所有传递的对象都是从链首传递过来,上一节点是什么没有关系,只要按照自己的逻辑处理就成。而触发链模式就不同了,它的上下级关系很亲密,下级对上级顶礼膜拜,上级对下级绝对信任,链中的任意两个相邻节点都是一个牢固的独立团体。
- 消息的分销渠道不同
在责任链模式中,一个消息从链首传递进来后,就开始沿着链条向链尾运动,方向是单一的、固定的;而触发链模式则不同,由于它采用的是观察者模式,所以有非常大的灵活性,一个消息传递到链首后,具体怎么传递是不固定的,可以以广播方式传递,也可以以跳跃方式传递,这取决于处理消息的逻辑。
跨战区PK
策略模式VS桥梁模式
策略模式是一个行为模式,旨在封装一系列的行为。而桥梁模式则是解决在不破坏封装的情况下如何抽取出它的抽象部分和实现部分,它的前提是不破坏封装,让抽象部分和实现部分都可以独立地变化,只要继承了抽象类就可以继续扩展,它的主旨是建立一个不破坏封装性的可扩展架构。
简单来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。桥梁模式更多的是将抽象和实现解耦,桥接的实现可以一直重写去适应扩展,策略更多的是实现(算法)的替换。
门面模式VS中介者模式
门面模式为复杂的子系统提供一个统一的访问界面,它定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模块深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对象来封装一系列同事对象的交互行为,它使各对象之间不再显式地引用,从而使其耦合松散,建立一个可扩展的应用架构。
门面模式是以封装和隔离为主要任务,而中介者模式则是以调和同事类之间的关系为主,因为要调和,所以具有了部分的业务逻辑控制两者的主要区别如下:
- 功能区别。
门面模式只是增加了一个门面,它对子系统来说没有增加任何的功能,子系统若脱离门面模式是完全可以独立运行的。而中介者模式则增加了业务功能,它把各个同事类中的原有耦合关系移植到了中介者,同事类不可能脱离中介者而独立存在,除非是想增加系统的复杂性和降低扩展性。
- 知晓状态不同。
对门面模式来说,子系统不知道有门面存在,而对中介者来说,每个同事类都知道中介者存在,因为要依靠中介者调和同事之间的关系,它们对中介者非常了解。
- 封装程度不同
门面模式是一种简单的封装,所有的请求处理都委托给子系统完成,而中介者模式则需要有一个中心,由中心协调同事类完成,并且中心本身也完成部分业务,它属于更进一步的业务功能封装。
包装模式群PK
包装模式包括:装饰模式、适配器模式、门面模式、代理模式、桥梁模式
- 代理模式:有事找代理,代理去找实现。主要用在不希望展示一个对象内部细节的场景中
- 装饰模式:装饰模式是一种特殊的代理模式,它倡导的是在不改变接口的前提下为对象增强功能,或者动态添加额外职责。(抽象+继承,抽象实现接口,实现中直接调用引用接口实例的方法,装饰者继承抽象父类,重写方法完成装饰后执行父类方法。)
- 适配器模式:主要意图是接口转换。继承被适配者实现目标接口,对被适配者加工实现目标。
桥接模式:抽象和实现剥离,实现可以不受抽象的约束。在抽象层产生耦合,解决的是自行扩展的问题,它可以使两个有耦合关系的对象互不影响地扩展。抽象和实现的桥梁,抽象依赖实现,抽象的子类通过重写调用实现类的扩展。主要解决抽象和实现的解耦,即抽象和实现可以自由扩展而不会对系统造成影响。
- 门面模式:不具有任何的业务逻辑,仅仅是一个访问复杂系统的快速通道。存在仅仅是为了方便
设计模式混编
命令模式+责任链模式
命令模式作为责任链模式的排头兵,由命令模式分发具体的消息到责任链模式。
工厂方法模式+策略模式
- 策略模式。策略模式负责对扣款策略进行封装,保证两个策略可以自由切换,而且日后增加扣款策略也非常简单容易。
- 工厂方法模式。修正策略模式必须对外暴露具体策略的问题,由工厂方法模式直接产生一个具体策略对象,而其他模块则不需要依赖具体的策略。
观察者模式+中介者模式
- 观察者模式解决了事件如何通知处理者的问题,而且观察者模式还有一个优点是可以有多个观察者,也就是我们的架构是可以有多层级、多分类的处理者。
- 中介者模式。解耦。
扩展篇
规格模式
规格模式是组合模式的一种特殊应用,它允许您定义一组条件或规格来评估对象。它提供了一种将业务规则或条件封装到独立的规格类中的方式,可以将这些规格组合并对对象进行评估,以确定它们是否满足指定的条件。
使用场景:
多个对象中筛选查找,或者业务规则不适于放在任何已有实体或值对象中,而且规则的变化和组合会掩盖那些领域对象的基本含义,或者是想自己编写一个类似LINQ的语言工具的时候 就可以照搬这部分代码。
对象池模式
通过循环使用对象,减少资源在初始化和释放时的昂贵损耗。
简单地说,在需要时,从池中提取;不用时,放回池中,等待下一个请求。典型例子是连接池和线程池。
public abstract class ObjectPool<T> {
//容器,容纳对象
private Hashtable<T, ObjectStatus> pool = new Hashtable<T, ObjectStatus>();
//初始化时创建对象,并放入到池中
public ObjectPool() {
pool.put(create(), new ObjectStatus());
}
//从Hashtable中取出空闲元素
public synchronized T checkOut() {
//这是最简单的策略
for (T t : pool.keySet()) {
if (pool.get(t).validate()) {
pool.get(t).setUsing();
return t;
}
}
return null;
}
//归还对象
public synchronized void checkIn(T t) {
pool.get(t).setFree();
}
class ObjectStatus {
//占用
public void setUsing() {
}
//释放
public void setFree() {
//注意:若T是有状态,则需要回归到初始化状态
}
//检查是否可用
public boolean validate() {
return false;
}
}
//创建池化对象
public abstract T create();
}
在实际应用中还需要考虑池的最小值、最大值、池化对象状态(若有的话,需要重点考虑)、异常处理(如满池情况)等方面,特别是池化对象状态,若是有状态的业务对象则需要重点关注。
雇工模式
雇工模式也叫做仆人模式(Servant Design Pattern)。
雇工模式是行为模式的一种,它为一组类提供通用的功能,而不需要类实现这些功能,它是命令模式的一种扩展。
实现
核心是接口实例的传递
package employee;
/**
* 通用功能
*/
interface IServiced {
//具有的特质或功能
public void serviced();
}
/**
* 具体功能
*/
class ConcreteServiced1 implements IServiced {
public void serviced() {
System.out.println("Serviced 1 doing");
}
}
class ConcreteServiced2 implements IServiced {
public void serviced() {
System.out.println("Serviced 2 doing");
}
}
/**
* 雇工类
*/
class Servant {
//服务内容
public void service(IServiced serviceFuture) {
serviceFuture.serviced();
}
}
public class EmployeePattern {
public static void main(String[] args) {
Servant servant = new Servant();
servant.service(new ConcreteServiced1());
servant.service(new ConcreteServiced2());
}
}
黑板模式
允许消息的读写同时进行,广泛地交互消息。
主要解决的问题是消息的生产者和消费者之间的耦合问题,它的核心是消息存储(黑板),它存储所有消息,并可以随时被读取。
- 拉模式。使用数据库
- 推模式。使用消息队列
空对象模式
通过实现一个默认的无意义对象来避免null值出现,简单地说,就是为了避免在程序中出现null值判断而诞生的一种常用设计方法。
空对象模式是通过空代码实现一个接口或抽象类的所有方法,以满足开发需求,简化程序