广州网站营销seo费用,做网站专题怎样添加微博,wordpress win2008 r2,聊城网站百度推广文章目录 设计模式分类七大原则单一职责原则开闭原则里氏替换原则接口隔离原则依赖倒置原则合成复用原则迪米特法则 单例懒汉式#xff1a;饿汉式#xff1a;线程安全版懒汉式 工厂模式简单工厂模式#xff1a;工厂方法模式#xff1a;抽象工厂模式#xff1a; 代理模式静… 文章目录 设计模式分类七大原则单一职责原则开闭原则里氏替换原则接口隔离原则依赖倒置原则合成复用原则迪米特法则 单例懒汉式饿汉式线程安全版懒汉式 工厂模式简单工厂模式工厂方法模式抽象工厂模式 代理模式静态代理动态代理虚拟代理 装饰器模式策略模式观察者模式责任链模式 本文主讲八种设计模式分别是单例、工厂方法、抽象工厂、代理模式、装饰器模式、策略模式、观察者模式、责任链模式。 概念 设计模式是一种解决软件设计中常见问题的经验总结它是从实践中产生的并被广泛验证的软件开发最佳实践。设计模式提供了一种通用的解决方案可以解决软件设计中常见的问题和困难可以提高程序员的开发效率和代码的可重用性。设计模式是一种被抽象化和概括化的描述它可以让一个系统或一个组件更加灵活、可扩展、易于维护和理解。设计模式不是一种能够直接使用的代码而是一种思想和经验的体现。在软件开发中设计模式是非常重要的因为它可以帮助开发人员更加系统地理解和处理问题提高软件的质量和可靠性降低开发成本和风险。
设计模式分类
设计模式根据工作的目的分为创建型模式、结构型模式和行为型模式三类。
创建型模式 单例模式、工厂方法模式、抽象工厂模式、创建者模式、原型模式。
结构型模式 适配器模式、代理模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
本文主讲八种设计模式分别是单例、工厂方法、抽象工厂、代理模式、装饰器模式、策略模式、观察者模式、责任链模式。
七大原则
单一职责原则
单一职责原则指的是一个类或对象应该只有一个单一的功能并且该功能被彻底封装在这个类的对象中。这样可以避免类或对象承担过多的职责提高其内聚性和可维护性。
开闭原则
开闭原则指的是一个模块或者对象应该对扩展开放对修改关闭。也就是说在不修改原有代码的基础上通过扩展来实现新的功能这样可以避免对原有代码产生影响提高代码的可维护性和扩展性。
里氏替换原则
里氏替换原则指的是派生类子类对象应该能够替代其基类父类的对象而程序仍能够正确地执行。也就是说在设计时应该保证所有派生类兼容其基类而不会破坏系统的一致性和稳定性。
接口隔离原则
接口隔离原则指的是一个类或对象应该只暴露其需要使用的接口而不需要暴露所有的接口。这样可以避免不必要的依赖提高代码的灵活性和可维护性。
依赖倒置原则
依赖倒置原则指的是高层模块不应该依赖于底层模块它们都应该依赖于抽象接口。抽象接口应该定义在高层模块中底层模块通过实现该接口来与高层模块进行交互。这样可以松耦合不同的模块提高代码的可维护性和可扩展性。
合成复用原则
合成复用原则指的是在设计时应该优先使用对象组合而不是继承关系来实现代码的复用。这样可以避免继承关系的耦合性和局限性提高代码的灵活性和可维护性。
迪米特法则
迪米特法则最少知识原则指的是一个对象应该有最小的依赖关系只依赖于直接需要使用的类或对象。也就是说如果一个对象要调用另一个对象的某个方法应该尽量避免直接调用该方法而是通过中间对象来调用。这样可以减少对象之间的耦合提高代码的灵活性和可维护性。
单例
Java单例设计模式是一种创建对象的设计模式确保在应用程序中只有一个实例。
优点
确保一个类只有一个实例避免了重复创建对象的开销。 提供全局访问点方便在整个程序中访问该实例。 可以控制对象的数量和生命周期确保对象的一致性和可靠性。 可以避免多线程冲突的问题在多线程环境下保证对象的唯一性。 缺点
单例模式可能导致紧耦合使得程序的扩展性和可维护性变差。 单例模式不适合需要多个相互独立的实例的情况违背了单一职责原则。 单例模式的实现需要考虑线程安全增加了实现的复杂性。
单例设计模式主要分为两种懒汉式延迟加载 与 饿汉式启动加载
懒汉式 延迟加载即只有在第一次使用时才会创建实例。 线程不安全如果多个线程同时调用getInstance()方法会出现多个实例的情况。 适合单线程或者在实例化时没有多线程环境的场景。 代码
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {}public static LazySingleton getInstance() {if (instance null) {instance new LazySingleton();}return instance;}
}饿汉式 立即加载即在类加载时就创建实例。 线程安全由于实例是静态变量所以在多线程环境下也只会创建一个实例。 适合多线程环境和要求唯一实例的场景。 代码
public class EagerSingleton {private static final EagerSingleton instance new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() {return instance;}
}线程安全版懒汉式
为了保证线程安全可以通过以下几种方式实现懒汉式单例模式
1.给getInstance方法加锁使用synchronized保证同步。
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {}public static synchronized LazySingleton getInstance() {if (instance null) {instance new LazySingleton();}return instance;}
}2.使用双重锁定检查(double-checked locking)减少锁竞争的概率。
public class LazySingleton {private volatile static LazySingleton instance;private LazySingleton() {}public static LazySingleton getInstance() {if (instance null) {synchronized (LazySingleton.class) {if (instance null) {instance new LazySingleton();}}}return instance;}
}3.使用静态内部类可以保证线程安全以及延迟加载。
public class LazySingleton {private LazySingleton() {}private static class LazyHolder {private static final LazySingleton INSTANCE new LazySingleton();}public static LazySingleton getInstance() {return LazyHolder.INSTANCE;}
}工厂模式
工厂方法模式Factory Method Pattern是一种创建类模式它提供了一种创建对象的最佳方式。通常情况下我们会使用new操作符来直接创建某个类的对象但有时候我们希望在不暴露对象创建的细节的前提下实现对象的实例化。这个时候工厂方法模式就可以派上用场了。
工厂方法模式定义了一个工厂接口它负责定义工厂方法。具体的工厂类将实现这个接口并根据不同的需求实现不同的工厂方法用于创建不同的对象。
根据工厂方法的不同实现方式我们可以将工厂方法分为三种简单工厂模式、工厂方法模式、抽象工厂模式。
简单工厂模式
简单工厂模式是一种比较基础的工厂方法实现方式。它通过一个具体的工厂类对多个不同的产品类进行实例化并将实例化过程封装起来。
优点 封装性好降低客户端复杂度和维护成本动态获取对象。 缺点 扩展性差添加新对象可能违反开闭原则工厂类代码量过大。
为了方便理解下面举个栗子 假设我们需要购买一双跑步鞋我们可以到一家销售运动鞋的店面。这家店面就是一个工厂它会提供不同的品牌和型号的跑步鞋比如安踏、特步、鬼子宁等。我们只需要根据鞋子的品牌和型号告诉店员店员就可以直接从工厂拿到相应的鞋子给我们。 实现
public class TestSimpleFactoryPattern {public static void main(String[] args) {ShoeFactory factory new ShoeFactory();// 生产慢跑鞋Shoe antaRunning factory.createShoe(安踏, 慢跑鞋);antaRunning.describe();Shoe xtepRunning factory.createShoe(特步, 慢跑鞋);xtepRunning.describe();// 生产篮球鞋Shoe antaBasketball factory.createShoe(安踏, 篮球鞋);antaBasketball.describe();Shoe xtepBasketball factory.createShoe(特步, 篮球鞋);xtepBasketball.describe();}
}// 定义鞋的工厂类
public class ShoeFactory {public Shoe createShoe(String brand, String type) {if (brand.equals(安踏)) {if (type.equals(慢跑鞋)) {return new AntaRunningShoe();} else if (type.equals(篮球鞋)) {return new AntaBasketballShoe();}} else if (brand.equals(特步)) {if (type.equals(慢跑鞋)) {return new XtepRunningShoe();} else if (type.equals(篮球鞋)) {return new XtepBasketballShoe();}}return null;}
}// 鞋的接口
public interface Shoe {void describe();
}// 安踏跑步鞋
public class AntaRunningShoe implements Shoe {public void describe() {System.out.println(这是一双安踏跑步鞋。);}
}// 特步跑步鞋
public class XtepRunningShoe implements Shoe {public void describe() {System.out.println(这是一双特步跑步鞋。);}
}// 安踏篮球鞋
public class AntaBasketballShoe implements Shoe {public void describe() {System.out.println(这是一双安踏篮球鞋。);}
}// 特步篮球鞋
public class XtepBasketballShoe implements Shoe {public void describe() {System.out.println(这是一双特步篮球鞋。);}
}工厂方法模式
工厂方法模式是指针对简单工厂模式的缺点进行的改进。定义一个用于创建对象的接口让子类决定实例化哪个类。工厂方法模式可以让一个类的实例化延迟到其子类中进行。这个模式就好比是一个工厂它有多个制造产品的子工厂每个子工厂可以制造多种类型的产品。
栗子 还是需要购买一双跑步鞋。我们可以选择到安踏专卖店去购买。在安踏专卖店内每一种型号的鞋子都对应一个工厂类比如板鞋系列对应板鞋工厂类。我们告诉店员我们要买一双板鞋XXX鞋子店员就会根据我们的需求从板鞋工厂类中创建相应型号的板鞋XXX鞋子。 实现
public class TestFactoryMethodPattern {public static void main(String[] args) {ShoeFactory antaRunningFactory new AntaRunningShoeFactory();Shoe antaRunningShoe antaRunningFactory.createShoe();antaRunningShoe.describe();ShoeFactory xtepBasketballFactory new XtepBasketballShoeFactory();Shoe xtepBasketballShoe xtepBasketballFactory.createShoe();xtepBasketballShoe.describe();}
}// 鞋的工厂接口
public interface ShoeFactory {Shoe createShoe();
}// 安踏跑步鞋工厂
public class AntaRunningShoeFactory implements ShoeFactory {public Shoe createShoe() {return new AntaRunningShoe();}
}// 特步篮球鞋工厂
public class XtepBasketballShoeFactory implements ShoeFactory {public Shoe createShoe() {return new XtepBasketballShoe();}
}// 鞋的接口
public interface Shoe {void describe();
}// 安踏跑步鞋
public class AntaRunningShoe implements Shoe {public void describe() {System.out.println(这是一双安踏跑步鞋。);}
}// 特步篮球鞋
public class XtepBasketballShoe implements Shoe {public void describe() {System.out.println(这是一双特步篮球鞋。);}
}抽象工厂模式
抽象工厂模式是一种对工厂方法模式进行进一步抽象和拓展的模式。在抽象工厂模式中每个具体的工厂类不仅可以创建一种产品而是可以创建一族产品例如某一个品牌的多种型号的手机。抽象工厂模式保持了工厂方法模式的优点同时通过提高代码抽象层级实现了代码的可维护性和扩展性。
栗子 继续需要购买鞋子但这一次我们需要装备整套打篮球的鞋子。我们可以到一家提供篮球装备的运动鞋店购买。这家店提供一站式服务包括高帮篮球鞋、宽扣篮球鞋、透气篮球鞋等多种类型的篮球鞋还提供相应的配件比如脚踝护具、前掌垫等。这家店是一个抽象工厂每种类型的篮球鞋和对应的配件都属于一个产品族而每个产品族都由一个具体的工厂类生产比如高帮篮球鞋对应HighTopBasketballShoeFactory。我们可以根据自己的需求选择一套合适的篮球装备。 实现
public class TestAbstractFactoryPattern {public static void main(String[] args) {ShoeFactory highTopBasketballShoeFactory new HighTopBasketballShoeFactory();BasketballShoe highTopBasketballShoe highTopBasketballShoeFactory.createBasketballShoe();Accessories highTopBasketballAccessories highTopBasketballShoeFactory.createAccessories();highTopBasketballShoe.describe();highTopBasketballAccessories.describe();ShoeFactory lowTopBasketballShoeFactory new LowTopBasketballShoeFactory();BasketballShoe lowTopBasketballShoe lowTopBasketballShoeFactory.createBasketballShoe();Accessories lowTopBasketballAccessories lowTopBasketballShoeFactory.createAccessories();lowTopBasketballShoe.describe();lowTopBasketballAccessories.describe();}
}// 鞋子工厂接口
public interface ShoeFactory {BasketballShoe createBasketballShoe(); // 创建篮球鞋Accessories createAccessories(); // 创建篮球配件
}// 高帮篮球鞋工厂
public class HighTopBasketballShoeFactory implements ShoeFactory {public BasketballShoe createBasketballShoe() {return new HighTopBasketballShoe();}public Accessories createAccessories() {return new HighTopBasketballAccessories();}
}// 低帮篮球鞋工厂
public class LowTopBasketballShoeFactory implements ShoeFactory {public BasketballShoe createBasketballShoe() {return new LowTopBasketballShoe();}public Accessories createAccessories() {return new LowTopBasketballAccessories();}
}// 篮球鞋接口
public interface BasketballShoe {void describe();
}// 高帮篮球鞋
public class HighTopBasketballShoe implements BasketballShoe {public void describe() {System.out.println(这是一双高帮篮球鞋。);}
}// 低帮篮球鞋
public class LowTopBasketballShoe implements BasketballShoe {public void describe() {System.out.println(这是一双低帮篮球鞋。);}
}// 配件接口
public interface Accessories {void describe();
}// 高帮篮球鞋配件
public class HighTopBasketballAccessories implements Accessories {public void describe() {System.out.println(这是一套高帮篮球鞋配件。);}
}// 低帮篮球鞋配件
public class LowTopBasketballAccessories implements Accessories {public void describe() {System.out.println(这是一套低帮篮球鞋配件。);}
}代理模式
代理模式是一种结构型设计模式它允许创建一个代理对象代理对象可以控制对另一个对象的访问。代理对象起到一个中间层的作用它拦截对实际对象的访问并且可以在访问实际对象前后进行一些操作。
优点
职责分离代理对象可以处理与实际对象无关的事务从而将业务逻辑分离出来降低代码复杂度和耦合度。访问控制代理对象可以控制对实际对象的访问从而保护实际对象的安全性。延迟加载代理对象可以延迟实际对象的加载从而提高系统的性能和响应速度。缓存管理代理对象可以缓存实际对象避免重复创建和销毁实际对象从而降低系统开销。
缺点
增加复杂度在系统中引入了一个额外的代理层增加了系统的复杂度和代码量。性能损失由于代理对象需要拦截访问对系统的性能有一定的影响。代码维护增加了代理对象的维护工作需要对代理对象和实际对象两个部分进行管理。
代理模式有三种常见实现方式分别是静态代理、动态代理和虚拟代理。
静态代理
也称为编译时代理在编译时就确定代理对象和实际对象的关系。在静态代理中代理对象和实际对象实现相同的接口代理对象通过调用实际对象的方法来完成其操作。
优点 实现简单可以避免动态代理产生的运行时性能消耗。 缺点 需要手动编写代理类当实际对象的方法发生变化时代理类也需要修改。
举例 假设有一个计算器接口有加、减、乘、除四个方法实现加法和减法。我们可以创建一个代理类实现计算器接口代理类中除了实现加减法之外还可以进行日志记录、参数验证等操作。使用静态代理时我们需要手动编写代理类。 实现
// 计算器接口
interface Calculator {int add(int a, int b);int sub(int a, int b);
}// 计算器代理类
class CalculatorProxy implements Calculator {private final Calculator calculator;public CalculatorProxy(Calculator calculator) {this.calculator calculator;}Overridepublic int add(int a, int b) {// 记录计算日志System.out.println(计算器正在进行加法运算...);// 调用实际对象的方法int result calculator.add(a, b);// 记录计算结果System.out.printf(计算结果%d\n, result);return result;}Overridepublic int sub(int a, int b) {// 记录计算日志System.out.println(计算器正在进行减法运算...);// 调用实际对象的方法int result calculator.sub(a, b);// 记录计算结果System.out.printf(计算结果%d\n, result);return result;}
}// 实现计算器接口的类
class SimpleCalculator implements Calculator {Overridepublic int add(int a, int b) {return a b;}Overridepublic int sub(int a, int b) {return a - b;}
}public class StaticProxyDemo {public static void main(String[] args) {// 创建实际对象Calculator calculator new SimpleCalculator();// 创建代理对象Calculator proxy new CalculatorProxy(calculator);// 使用代理对象进行计算proxy.add(1, 2); // 输出计算器正在进行加法运算...计算结果3proxy.sub(5, 3); // 输出计算器正在进行减法运算...计算结果2}
}动态代理
也称为运行时代理在运行时根据需要动态生成代理对象。在动态代理中代理对象不需要实现与实际对象相同的接口而是通过Java中的反射机制在运行时生成代理对象动态代理常常用于AOP面向切面编程。
优点 无需手动编写代理类可以根据运行时要求灵活地生成代理对象。 缺点 生成代理对象的性能消耗较高在运行时增加了系统的负担。
举例 如果我们需要实现更多的操作例如计算时间、返回结果加密等手写代理类的工作量将变得很大。这时我们可以使用动态代理使用Java的Proxy类和InvocationHandler接口在运行时动态生成代理类。这样可以避免手动编写代理类的麻烦也避免了编写多个代理类的代码笨重。 实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 计算器接口
interface Calculator {int add(int a, int b);int sub(int a, int b);
}// 实现计算器接口的类
class SimpleCalculator implements Calculator {Overridepublic int add(int a, int b) {return a b;}Overridepublic int sub(int a, int b) {return a - b;}
}public class DynamicProxyDemo {public static void main(String[] args) {// 创建实际对象Calculator calculator new SimpleCalculator();// 创建动态代理对象Calculator proxy (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), // 指定类加载器new Class[]{Calculator.class}, // 指定接口new CalculatorInvocationHandler(calculator)); // 指定处理器// 使用代理对象进行计算proxy.add(1, 2); // 输出计算时间0msproxy.sub(5, 3); // 输出计算时间0ms}
}// 计算器代理处理器
class CalculatorInvocationHandler implements InvocationHandler {private final Calculator calculator;public CalculatorInvocationHandler(Calculator calculator) {this.calculator calculator;}// 在调用代理对象的方法时会自动执行invoke方法Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 记录计算时间long start System.currentTimeMillis();Object result method.invoke(calculator, args); // 调用实际对象的方法long end System.currentTimeMillis();System.out.printf(计算时间%dms\n, (end - start));return result;}
}虚拟代理
在需要时才创建实际对象。虚拟代理常用于管理大型、复杂对象的创建如图片、音频、视频等避免在加载时就创建全部对象导致系统崩溃或性能低下。虚拟代理通过代理对象先加载占用较少资源的对象待需要时再进行实际对象的加载从而提升了系统的性能和响应速度。
优点 可以优化系统的性能和资源占用将实际对象的创建延迟到需要时再进行创建。 缺点 需要额外的代理对象实现会增加代码的复杂程度并且代理对象需要实现与实际对象相同的接口对于实现复杂的对象可能需要大量的开发工作。
举例 假设我们需要加载一张非常大的图片图片大小超过了我们内存的限制直接加载会导致内存溢出。这时我们可以使用虚拟代理将图片的加载推迟到需要时再进行。虚拟代理先加载小图片或者只加载部分图片当用户需要查看大图时再加载完整的图片。这样既避免了内存溢出也缩短了页面的加载时间提高了用户体验。 实现
import javax.swing.*;
import java.awt.*;// 图片接口
interface Image {void show();
}// 实现图片接口的类
class BigImage implements Image {private final String fileName;public BigImage(String fileName) {this.fileName fileName;// 加载大图片需要很长时间try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}Overridepublic void show() {// 显示大图片JFrame frame new JFrame();frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setSize(800, 600);ImageIcon icon new ImageIcon(fileName);JLabel label new JLabel(icon);frame.add(label, BorderLayout.CENTER);frame.setVisible(true);}
}// 图片代理类
class ImageProxy implements Image {private final String fileName;private Image image;public ImageProxy(String fileName) {this.fileName fileName;}Overridepublic void show() {if (image null) {// 只有当需要显示图片时才进行加载image new BigImage(fileName);}// 显示图片image.show();}
}public class VirtualProxyDemo {public static void main(String[] args) {// 创建代理对象Image image new ImageProxy(big_image.jpg);// 第一次显示图片需要加载image.show();// 第二次显示图片不需要加载image.show();}
}装饰器模式
装饰器模式Decorator Pattern是一种结构型设计模式它允许为一个对象动态地添加新的行为。该模式的意图是通过将对象包装在一层装饰器对象中来给对象添加新的行为和责任而不需要对原有的类进行修改。
在装饰器模式中有四个核心角色
抽象组件Component 定义了被装饰的对象的接口可以是一个抽象类或者接口它声明了一些基本操作具体的实现由具体组件类提供。
具体组件ConcreteComponent 实现了抽象组件的接口即被包装的原始对象它提供了基本操作的具体实现。
抽象装饰器Decorator 包含一个指向抽象组件对象的引用它是所有装饰器的基类它可以是一个抽象类或者接口。
具体装饰器ConcreteDecorator 实现抽象装饰器的接口即具体的装饰器对象通常会将抽象装饰器作为成员变量并在自己的操作中调用抽象装饰器的方法然后再进行自己的操作。
优点
可以动态地给一个对象添加更多的责任而不需要修改原有的类。
可以将多个装饰器对象进行嵌套从而实现更复杂的功能。
增加装饰器对象和具体组件对象的独立性客户端可以根据需要自由地选择所需要的装饰器对象而不需要关心具体组件对象的变化。
可以避免装饰器类和被装饰的类之间出现继承上的耦合关系从而更好地实现了松耦合。
缺点
对于装饰器对象和具体组件对象的接口有些限制必须是同一个类型或接口。
装饰器模式增加了许多子类如果过度使用会增加系统的复杂度。
举例 假设有一家汽车制造商他们生产出一种基础型号的汽车它有基本的功能和特性比如引擎、轮胎、座椅等等。然而不同的客户都有不同的需求和喜好他们想要为自己的汽车添加一些额外的功能或特性比如更高的速度、更舒适的座椅、更好的音响、更个性化的外观等等。汽车制造商可以通过汽车定制服务来满足客户的这些需求。 如果客户想要一辆更快的车汽车制造商可以为基础型号的汽车添加一个名为“SportsCar”的装饰器这个装饰器可以为汽车添加更强大的引擎、更快的速度和更好的悬挂系统。如果客户想要一辆更豪华的车我们可以为基础型号的汽车添加一个名为“LuxuryCar”的装饰器这个装饰器可以为汽车添加更舒适的座椅、更好的音响和更高级的内饰。 因此汽车定制服务使用装饰器模式可以帮助汽车制造商满足不同客户的需求同时还能保持代码的简洁和易于维护。 代码实现
// 汽车接口定义了汽车的基本功能
public interface Car {void assemble();
}// 基础型号的汽车实现类
public class BasicCar implements Car {Overridepublic void assemble() {System.out.println(Assembling basic car parts.);}
}// 汽车的装饰器
public abstract class CarDecorator implements Car {// 持有原始汽车的引用protected Car car;// 通过构造器注入原始汽车的引用public CarDecorator(Car car) {this.car car;}// 实现汽车接口的 assemble() 方法调用原始汽车的 assemble() 方法Overridepublic void assemble() {car.assemble();}
}// 为汽车添加运动功能的装饰器
public class SportsCar extends CarDecorator {public SportsCar(Car car) {super(car);}// 重写 assemble() 方法为汽车添加运动功能Overridepublic void assemble() {super.assemble();System.out.println(Adding features of Sports Car.);}
}// 为汽车添加豪华功能的装饰器
public class LuxuryCar extends CarDecorator {public LuxuryCar(Car car) {super(car);}// 重写 assemble() 方法为汽车添加豪华功能Overridepublic void assemble() {super.assemble();System.out.println(Adding features of Luxury Car.);}
}// 为汽车添加 SUV 功能的装饰器
public class SUV extends CarDecorator {public SUV(Car car) {super(car);}// 重写 assemble() 方法为汽车添加 SUV 功能Overridepublic void assemble() {super.assemble();System.out.println(Adding features of SUV.);}
}// 客户端代码使用已经装饰好的汽车
public class Client {public static void main(String[] args) {// 创建基础型号的汽车Car car new BasicCar();// 为汽车添加运动功能car new SportsCar(car);// 为汽车添加豪华功能car new LuxuryCar(car);// 为汽车添加 SUV 功能car new SUV(car);// 调用汽车的 assemble() 方法输出装饰后的汽车特性和功能car.assemble();}
}在这段代码中我们定义了一个 Car 接口和一个 BasicCar 类他们分别代表汽车的接口和基础型号的汽车。我们还定义了 CarDecorator 抽象类以及具体的 SportsCar、LuxuryCar 和 SUV 装饰器类它们继承了 CarDecorator 类并重写了 assemble() 方法来添加特定的汽车功能。
在 Client 类中我们使用基础型号的汽车分别添加了运动、豪华和 SUV 的功能并调用了装饰后的汽车的 assemble() 方法来输出装饰后的汽车特性和功能。
策略模式
在策略模式中我们定义了一系列的策略每个策略都代表着一种算法。这些策略可以在运行时动态替换从而允许我们在不同的情境下选择不同的算法来完成相同的任务。这种可替换性的特性使得策略模式非常适用于那些需要在多种算法中进行选择的情况。
策略模式通常由三部分构成
客户端(Context) 客户端定义了算法策略的接口并在运行时设置具体的策略实现对象。客户端通过策略接口与具体策略实现对象交互调用相应的算法。
策略接口(Strategy) 策略接口定义了一系列算法策略的公共接口。它是所有具体策略类的基类。
具体策略(ConcreteStrategy) 具体策略类实现了策略接口定义了一种具体的算法实现。客户端可以选择不同的具体策略类来完成相同的任务。
策略模式的优点在于它提供了一种高度灵活的方式来处理不同的算法实现。由于策略接口定义了算法的公共接口所以可以很容易地在不同的策略之间进行切换。这种可替换性的特性使得策略模式非常适用于那些需要动态改变算法的场景。此外策略模式的实现通常使用组合而非继承实现这样可以避免继承的种种问题包括类层次结构的臃肿和复杂性。
举例 假设你是一家酒店的经理你想要提供给客人多种早餐选择。有些客人可能想要有营养的早餐而有些客人则可能想要丰盛的早餐。为了满足不同客人的需求你可以使用策略模式。具体的实现步骤如下 首先你需要定义一个早餐接口该接口包含一个 prepare() 方法用于准备早餐。 接着你需要定义一些具体的早餐类实现早餐接口并重写 prepare() 方法。例如你可以实现一个豪华早餐类其中包含肉类、鸡蛋、牛奶等丰富的食物。又或者你可以实现一个健康早餐类其中包含水果、麦片、酸奶等营养丰富的食物。 最后你需要在客户端代码(例如酒店前台)中创建一个早餐对象并在运行时选择具体的早餐实现。例如如果客人要求豪华早餐你可以创建一个豪华早餐对象并设置为客人的早餐选择。 通过使用策略模式你可以灵活地满足不同客人的需求允许客人在多种早餐选择中进行选择并在需要时轻松更改早餐实现。 代码实现
FunctionalInterface
public interface Breakfast {void prepare();
}public class BreakfastClient {public static void main(String[] args) {// 客人要求豪华早餐Breakfast deluxeBreakfast () - System.out.println(豪华早餐煎饼、面条、炒饭、牛奶、鸡蛋、培根等丰富的食物。);deluxeBreakfast.prepare();// 客人要求健康早餐Breakfast healthyBreakfast () - System.out.println(健康早餐水果沙拉、麦片、酸奶、全麦面包等营养丰富的食物。);healthyBreakfast.prepare();}
}观察者模式
观察者模式的核心是通过定义一个被观察者Subject对象该对象负责维护一个确定数量的依赖于该对象的观察者Observer依赖于该被观察者对象的状态的变化将会通知所有的观察者对象进行相应的更新。
该模式由以下角色组成
被观察者Subject 也称为主题它是一个抽象类或接口具有添加、删除和通知观察者的方法。
具体被观察者Concrete Subject 该对象继承或实现了被观察者接口它具有一个状态当该状态发生改变时会通知所有的观察者对象。
观察者Observer 定义一个更新接口该接口可以被具体观察者实现。该接口定义了当被观察者状态发生变化时所要执行的操作。
具体观察者Concrete Observer 实现更新接口以便在状态发生变化时更新自己的状态。
观察者模式的优点是减少了对象间的耦合使得一个对象的状态变化可以让其他对象感知到并进行相应的操作。同时被观察者对象不需要知道观察者对象的具体实现可以方便地添加或删除观察者对象。缺点是当观察者对象较多时被观察者对象进行通知的时间可能会变长同时观察者对象也应该避免相互之间的循环依赖。
来看一个通俗易懂的例子报纸订阅。 报纸订阅的过程包含一个发布者被观察者和订阅者观察者。当新的一期报纸出版时发布者会将消息发送给所有订阅该报纸的用户因此这里的观察者们订阅者可以在发布者发生变化时自动获取信息。 具体实现如下
首先创建一个报纸发行者被观察者类 Publisher
import java.util.ArrayList;
import java.util.List;public class Publisher {private ListSubscriber subscribers new ArrayList();public void subscribe(Subscriber subscriber) {subscribers.add(subscriber);}public void unsubscribe(Subscriber subscriber) {subscribers.remove(subscriber);}public void notifySubscribers(String message) {subscribers.forEach(subscriber - subscriber.onUpdate(message));}
}在这个类中我们实现了 subscribe 、unsubscribe 和 notifySubscribers 3个方法分别用于增加/删除订阅者以及向订阅者发布新信息。
关于每个具体的订阅者观察者 Subscriber 如下
public interface Subscriber {void onUpdate(String message);
}public class NewspaperSubscriber implements Subscriber {private String name;public NewspaperSubscriber(String name) {this.name name;}public void subscribe(Publisher publisher) {publisher.subscribe(this);}public void unsubscribe(Publisher publisher) {publisher.unsubscribe(this);}Overridepublic void onUpdate(String message) {System.out.println(name receives message: message);}Overridepublic String toString() {return name;}
}每个订阅者拥有 subscribe 和 unsubscribe方法并通过它们将自己注册到或移除 Publisher 中。
最后我们可以在客户端代码中创建被观察者对象和观察者对象并建立它们之间的关系
public class Client {public static void main(String[] args) {Publisher publisher new Publisher();NewspaperSubscriber subscriber1 new NewspaperSubscriber(张三);NewspaperSubscriber subscriber2 new NewspaperSubscriber(李四);NewspaperSubscriber subscriber3 new NewspaperSubscriber(王五);subscriber1.subscribe(publisher);subscriber2.subscribe(publisher);subscriber3.subscribe(publisher);publisher.notifySubscribers(摆脱疫情影响经济全速前行);// 打出以下信息// 张三 receives message: 摆脱疫情影响经济全速前行// 李四 receives message: 摆脱疫情影响经济全速前行// 王五 receives message: 摆脱疫情影响经济全速前行subscriber2.unsubscribe(publisher);publisher.notifySubscribers(机场扩建工程已经开始动工于明年底完成);// 打出以下信息// 张三 receives message: 机场扩建工程已经开始动工于明年底完成// 王五 receives message: 机场扩建工程已经开始动工于明年底完成}
}在这个例子中我们建立了三个订阅者并将它们注册到 Publisher 中。当 Publisher 发送通知时所有订阅的订阅者都会接收到信息。我们还可以通过移除某个订阅者来停止通知。
责任链模式
责任链模式是一种行为型设计模式它通过创建一条具有顺序关系的处理对象链将多个处理对象串联起来并在这条链上传递请求直到有一个处理对象可以处理请求为止。
在责任链模式中请求发送者并不知道请求将会由哪个对象处理每个处理对象都有自己的处理逻辑如果当前对象能够处理请求则直接处理如果不能处理则将请求传递给下一个处理对象。通过这种方式责任链模式将请求发送者和接收者进行了解耦从而实现了系统的灵活性和可维护性。
责任链模式的角色通常包括以下几种
抽象处理对象Handler 定义了处理请求的接口并维护一个对下一个处理对象的引用。
具体处理对象Concrete Handler 实现了抽象处理对象的接口具体处理请求的逻辑。如果自己无法处理请求则将请求转发给下一个处理对象。
客户端Client 创建并连接具体处理对象。
责任链模式主要解决了多个处理者共同处理同一个请求的场景。通常应用于需要进行请求多阶段处理的场合例如工作流流程、权限校验等场景。
使用责任链模式时需要注意处理请求的链需要被正确地组织起来每个处理对象应该知道该请求是否由自己处理否则将可能导致请求无法得到处理或者被重复处理。同时需要注意避免链太长否则可能会降低系统的性能。
总体来说责任链模式是一种非常实用的设计模式可以帮助我们构建高内聚、低耦合的系统从而提高系统的可维护性和灵活性。
举例 请假审批。在这个例子中有多个级别的审批人员每个人员都可以进行审批但是只有在当前审批人无法处理请求的情况下才会将请求转发给下一个级别的审批人员。以下是相应的 /**
* 请假审批责任链模式
*/// 定义抽象审批人员类
abstract class Approver {protected Approver successor; // 下一个审批人员public void setSuccessor(Approver successor) {this.successor successor;}public abstract void processRequest(LeaveRequest request);
}// 定义具体审批人员类员工、经理、总经理
class Employee extends Approver {Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() 5) {System.out.println(员工可以审批该申请);} else {this.successor.processRequest(request);}}
}class Manager extends Approver {Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() 10) {System.out.println(经理可以审批该申请);} else {this.successor.processRequest(request);}}
}class GeneralManager extends Approver {Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() 15) {System.out.println(总经理可以审批该申请);} else {System.out.println(请假时间太长无人能够处理该申请);}}
}// 定义请假申请类
class LeaveRequest {private String name;private int days;public LeaveRequest(String name, int days) {this.name name;this.days days;}public String getName() {return name;}public int getDays() {return days;}
}public class Main {public static void main(String[] args) {// 构造请假请求对象LeaveRequest request new LeaveRequest(小明, 7);// 创建审批人员对象Approver emp new Employee();Approver mgr new Manager();Approver gm new GeneralManager();// 设置责任链关系emp.setSuccessor(mgr);mgr.setSuccessor(gm);// 将请假请求传递给处理链的第一个对象emp.processRequest(request);}
}在这个例子中定义了抽象的 Approver 类表示审批人员在具体实现中分别定义了 Employee、Manager、GeneralManager 类表示员工、经理、总经理等不同级别的审批人员。这些审批人员都继承自 Approver 抽象类并重写了 processRequest() 方法。在 processRequest() 方法中如果当前审批人员可以处理该请求就直接处理否则将请求转发给下一个级别的审批人员。
在 Main 类中首先构造请假请求对象 LeaveRequest然后依次构造具体审批人员对象最后将请求传递给处理链的第一个对象 Emp 进行处理。在代码运行时会依次在控制台输出每个审批人员的处理结果。 以上就是常用的八种设计模式了总之设计模式是面向对象编程的基础通过学习和应用设计模式我们可以更好地掌握面向对象思想和技巧更加精通软件开发。虽然设计模式并非一蹴而就需要花费大量的精力和时间去学习和理解但是这份努力将会为我们未来的编程生涯带来丰硕的成果。
如果你还想了解更多有关设计模式和面向对象编程方面的内容推荐一些经典的参考书籍
《设计模式可复用面向对象软件的基础》Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
《Head First 设计模式》Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
《重构改善既有代码的设计》Martin Fowler
感谢您的耐心阅读如果您有任何问题或建议请随时留言联系我。我会尽快回复您并进一步交流谢谢