实战:在项目中常用的8种设计模式

264 篇文章 0 订阅
52 篇文章 1 订阅

概叙

科普文:跟着Spring学设计模式-CSDN博客

https://zhouxx.blog.csdn.net/article/details/140463177

关于设计模式,参考上面两篇文章。

所谓 “设计模式”,就是一套反复被人使用或验证过的方法论。从抽象或者更宏观的角度上看,只要符合使用场景并且能解决实际问题,模式应该既可以应用在DDD中,也可以应用在设计模式中。

常用的8种设计模式

策略模式
工厂模式
单例模式
代理模式
工厂方法模式
观察者模式
模板方法模式
适配器模式

设计模式简单实现模板

场景: 商场搞活动,根据客户购买商品的金额,收费时给与不同的打折,比如,购买 金额>=2000 的打八折(0.8),金额 500 ~ 1000 的,打九折(0.9),购买金额 0 ~ 500 的九五折(0.95),根据不同的金额走不同计算策略逻辑。

1 策略模式

首先定义一个Strategy接口来表示一个策略:

public interface Strategy {

    /**
     * 采用策略
     */
    String strategy();

    /**
     * 计算方法逻辑
     */
    void algorithm();
}

其中strategy方法返回当前策略的唯一标识,algorithm则是该策略的具体执行的计算逻辑。

下面是Strategy接口的两个实现类:

public class ConcreteStrategyA implements Strategy {
    
    @Override
    public String strategy() {
        return StrategySelector.strategyA.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyA...");
    }
}

public class ConcreteStrategyB implements Strategy {

    @Override
    public String strategy() {
        return StrategySelector.strategyB.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyB...");
    }
}

public class ConcreteStrategyC implements Strategy {

    @Override
    public String strategy() {
        return StrategySelector.strategyC.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyC...");
    }
}

自定义策略选择枚举 **StrategySelector**:

@Getter
public enum StrategySelector {

    strategyA(1,"strategyA"),
    strategyB(2,"strategyB"),
    strategyC(3,"strategyC");
    
    private Integer code;
    private String strategy;

    StrategySelector(Integer code, String strategy) {
        this.code = code;
        this.strategy = strategy;
    }
}

然后定义一个StrategyRunner接口用来表示策略的调度器:

public interface StrategyRunner {
    void execute(String strategy);
}

execute方法内部通过判断strategy的值来决定具体执行哪一个策略。

public class StrategyRunnerImpl implements StrategyRunner {

    private static final List<Strategy> STRATEGIES = Arrays.asList(new ConcreteStrategyA(), new ConcreteStrategyB(), new ConcreteStrategyC());
    private static Map<String, Strategy> STRATEGY_MAP = Maps.newHashMap();

    static {
        STRATEGY_MAP = STRATEGIES.stream().collect(Collectors.toMap(Strategy::strategy, s -> s));
    }

    @Override
    public void execute(String strategy) {
        STRATEGY_MAP.get(strategy).algorithm();
    }
}

StrategyRunnerImpl内部,定义了一个STRATEGIES列表来保存所有Strategy实现类的实例,以及一个叫做STRATEGY_MAPMap来保存strategyStrategy实例之间的对应关系,static块中的代码用于从STRATEGIES列表构造STRATEGY_MAP。这样,在execute方法中就可以很方便地获取到指定strategyStrategy实例。

实现并运用策略模式

@Component
public class ConcreteStrategyA implements Strategy {
    
    @Override
    public String strategy() {
        return StrategySelector.strategyA.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyA...");
    }
}

@Component
public class ConcreteStrategyB implements Strategy {

    @Override
    public String strategy() {
        return StrategySelector.strategyB.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyB...");
    }
}

@Component
public class ConcreteStrategyC implements Strategy {

    @Override
    public String strategy() {
        return StrategySelector.strategyC.getStrategy();
    }

    @Override
    public void algorithm() {
        System.out.println("process with strategyC...");
    }
}

然后,定义一个StrategyConfig配置类,用于向容器注入一个StrategyRunner:

@Configuration
public class StrategyConfig {

    @Bean
    public StrategyRunner runner(List<Strategy> strategies) {
        Map<String, Strategy> strategyMap = strategies.stream().collect(Collectors.toMap(Strategy::strategy, s -> s));
        return flag -> strategyMap.get(flag).algorithm();
    }
}

不难发现,strategyRunner方法的实现,其中的逻辑与之前的StrategyRunnerImpl几乎完全相同,也是根据一个List<Strategy>来构造一个Map<String, Strategy>。只不过,这里的strategies列表不是我们自己构造的,而是通过方法参数传进来的。由于strategyRunner标注了Bean注解,因此参数上的List<Strategy>实际上是在Spring Boot初始化过程中从容器获取的,所以我们之前向容器中注册的那两个实现类会在这里被注入。

这样,我们再也无需操心系统中一共有多少个Strategy实现类,因为Spring Boot的自动配置会帮我们自动发现所有实现类。我们只需编写自己的Strategy实现类,然后将它注册进容器,并在任何需要的地方注入StrategyRunner

@Autowired private StrategyRunner strategyRunner;

然后直接使用strategyRunner就行了:

@RestController
@RequestMapping(value = "/designPatterns")
public class DesignPatternController {

    @Autowired
    private StrategyRunner strategyRunner;

    @GetMapping(value = "/algorithm")
    public void algorithm(@RequestParam("strategy") String strategy) {
        strategyRunner.execute(strategy);
    }
}

访问接口,控制台输出如下:

process with strategyA...

具体案例一:

策略模式在发送不同类型消息(如短信、邮件、推送通知等)的场景下非常适用,因为它允许你在运行时选择不同的消息发送策略。下面是一个使用策略模式实现的发送短信消息案例。

策略接口(MessageStrategy)

首先,定义一个策略接口,描述发送消息的行为:

public interface MessageStrategy {
    void sendMessage(String message, String recipient);
}

具体策略类(SMSStrategy)

然后,创建实现MessageStrategy接口的具体策略类,这里以发送短信为例:

public class SMSStrategy implements MessageStrategy {
    @Override
    public void sendMessage(String message, String recipient) {
        // 模拟发送短信的逻辑
        System.out.printf("Sending SMS to %s: %s%n", recipient, message);
    }
}

上下文类(MessageService)

接下来,定义一个上下文类,用于封装策略的使用,并在其中设置和执行策略:

public class MessageService {
    private MessageStrategy strategy;

    public MessageService(MessageStrategy strategy) {
        this.strategy = strategy;
    }

    public void sendMessage(String message, String recipient) {
        strategy.sendMessage(message, recipient);
    }
}

客户端代码

最后,客户端代码可以根据需要选择并使用不同的策略来发送短信:

public class StrategyPatternDemo {
    public static void main(String[] args) {
        // 创建一个发送短信的策略实例
        MessageStrategy smsStrategy = new SMSStrategy();

        // 创建消息服务,传入短信发送策略
        MessageService service = new MessageService(smsStrategy);

        // 使用服务发送短信
        service.sendMessage("Hello, this is a test SMS.", "+1234567890");
    }
}

在这个例子中,MessageStrategy接口定义了发送消息的策略,SMSStrategy是具体发送短信的实现。MessageService作为一个上下文,根据传入的策略执行对应的消息发送操作。客户端通过选择不同的策略实例,可以灵活地切换消息发送的方式,而无需修改消息服务的代码,这正是策略模式的优势所在。

具体案例二:

下面通过一个具体的案例来说明策略模式的应用:假设我们有一个鸭子模拟系统,鸭子可以有不同的叫声(呱呱叫、嘎嘎叫等),我们使用策略模式来动态改变鸭子的叫声行为。

首先,定义一个策略接口(DuckQuackBehavior):

public interface DuckQuackBehavior {
    void quack();
}

然后,创建几个实现了该接口的具体策略类:

// 呱呱叫行为
public class Quack implements DuckQuackBehavior {
    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}

// 嘎嘎叫行为
public class GagagaQuack implements DuckQuackBehavior {
    @Override
    public void quack() {
        System.out.println("嘎嘎叫");
    }
}

// 沉默不叫行为
public class Squeak implements DuckQuackBehavior {
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}

接下来,定义鸭子的抽象类和具体鸭子类,鸭子类包含对策略的引用:

// 鸭子抽象类
public abstract class Duck {
    protected DuckQuackBehavior quackBehavior;

    public void setQuackBehavior(DuckQuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    public void performQuack() {
        if (quackBehavior != null) {
            quackBehavior.quack();
        }
    }
}

// 具体鸭子类
public class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack(); // 默认呱呱叫
    }
}

public class RubberDuck extends Duck {
    public RubberDuck() {
        quackBehavior = new Squeak(); // 橡皮鸭吱吱叫
    }
}

最后,在客户端代码中,我们可以根据需要动态改变鸭子的行为:

public class StrategyPatternDemo {
    public static void main(String[] args) {
        Duck mallardDuck = new MallardDuck();
        mallardDuck.performQuack(); // 输出:呱呱叫

        mallardDuck.setQuackBehavior(new GagagaQuack()); // 改变行为
        mallardDuck.performQuack(); // 输出:嘎嘎叫

        Duck rubberDuck = new RubberDuck();
        rubberDuck.performQuack(); // 输出:吱吱叫
    }
}

这个例子展示了如何使用策略模式让鸭子在运行时改变它们的叫声行为,体现了行为的可替换性和扩展性。

2 简单工厂模式

举个场景例子:

用户支付场景,目前支持支付宝支付和微信支付,未来会新增银行卡,云闪付等方式。使用策略模式,每一种支付方式都是一种策略,根据用户传入的支付类型,创建不同的策略类,使用工厂模式,通过封装一个PaymentStrategyHandler策略处理类,其他系统直接通过一个统一的入口,进行该功能的调用,使用门面模式。

3.2.1 定义一个策略类:

public interface IPayment {

    /**
     * 支付
     *
     * @param paymentBody
     */
    Boolean pay(PaymentBody paymentBody);
}

public class AliPay implements IPayment {
    @Override
    public Boolean pay(PaymentBody paymentBody) {
        System.out.println("支付宝支付...");
        return Boolean.TRUE;
    }
}

public class WechatPay implements IPayment {
    @Override
    public Boolean pay(PaymentBody paymentBody) {
        System.out.println("微信支付...");
        return Boolean.TRUE;
    }
}

public class UnionPay implements IPayment {
    @Override
    public Boolean pay(PaymentBody paymentBody) {
        System.out.println("银联支付...");
        return Boolean.TRUE;
    }
}

3.2.2 创建策略工厂

package com.universal.core.designPatterns.factory;

import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ReflectUtil;
import com.universal.core.designPatterns.enums.PayStrategyEnum;
import org.springframework.stereotype.Component;

/**
 * Factory for payment methods
 */
@Component
public class PaymentFactory {

    public static IPayment getPayStrategy(String type) {
        // 1.通过枚举中的type获取对应的value
        String value = EnumUtil.getFieldBy(PayStrategyEnum::getValue, PayStrategyEnum::getType, type);
        // 2.使用反射机制创建对应的策略类
        IPayment payment = ReflectUtil.newInstance(value);
        return payment;
    }
}

3.3.3 定义策略枚举

/**
 * 支付策略枚举
 */
@Getter
public enum PayStrategyEnum {

    ZFB("ZFB", "com.universal.core.designPatterns.factory.impl.AliPay"),
    WX("WX", "com.universal.core.designPatterns.factory.impl.WechatPay"),
    UNION("UNION", "com.universal.core.designPatterns.factory.impl.UnionPay");
    String type;
    String value;

    PayStrategyEnum(String type, String value) {
        this.type = type;
        this.value = value;
    }
}

3.3.4 创建策略的上下文角色

@Data
public class PaymentContext {

    @Resource
    private IPayment paymentStrategy;

    public PaymentContext(IPayment paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public Boolean pay(PaymentBody paymentBody) {
        return this.paymentStrategy.pay(paymentBody);
    }
}

3.4.5 提供统一访问处理入口

package com.universal.core.designPatterns.factory;

import cn.hutool.core.util.EnumUtil;
import com.universal.core.designPatterns.enums.PayStrategyEnum;
import org.springframework.stereotype.Component;

@Component
public class PaymentStrategyHandler {

    public static Boolean pay(PaymentBody payBody) {
        if (!EnumUtil.contains(PayStrategyEnum.class, payBody.getType())) {
            throw new IllegalArgumentException("不支持的支付方式!");
        }
        // 1.获取支付策略对象
        IPayment payStrategy = PaymentFactory.getPayStrategy(payBody.getType());
        // 2.获取支付策略上下文
        PaymentContext paymentContext = new PaymentContext(payStrategy);
        // 3.进行支付
        return paymentContext.pay(payBody);
    }
}

3.4.6 创建Controller

@RestController
@RequestMapping(value = "/designPatterns")
public class DesignPatternController {

    @PostMapping("/pay")
    public Boolean pay(@RequestBody PaymentBody paymentBody){
        return PaymentStrategyHandler.pay(paymentBody);
    }

}

3 单例模式

代码演示:

//懒汉式(静态内部类)
class Singleton {
    private Singleton() {}

    //写一个静态内部类,该类中有一个静态属性Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static synchronized Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

4 代理模式

代理模式Proxy, 为其他对象提供一种代理以控制对这个对象的访问。

代理模式 实际上在平时中也运用的非常广泛,最经典的例子就是房东委托中介代理出租房子的案例,本文也是采用这个案例对代理模式进行解释和代码实现。

图片

image.png

代码实例:

创建一个Subject类:

/**
 * 活动类,目的是出租房子
 */
public interface Subject {

    /**
     * 租房接口
     */
    void rentHouse();
}

定义一个房东角色,现在活动类:

/**
 * 房东
 */
public class HouseOwner implements Subject {

    /**
     * 实现租房方法
     */
    @Override
    public void rentHouse() {
        System.out.println("房东成功出租了房子...");
    }
}

定义一个中介代理对象:

/**
 * 中介代理类
 *
 * 一般情况下我们不能直接联系到房东,所以需要提供一个代理类,即中介类
 */
public class HouseProxy implements Subject {

    private HouseOwner houseOwner = new HouseOwner();

    @Override
    public void rentHouse() {
        System.out.println("中介收取代理费,帮助房东出租房子...");
        houseOwner.rentHouse();
    }
}

模拟用户找中介租房子:

图片

image.png

5 工厂方法模式

上面我们也提到了简单工厂模式,那么工厂方法模式和简单工厂的区别在于哪里呢,其实,简单工厂模式的最大优点在于包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,相对于客户端来说,去除了与具体产品的依赖。

工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法是一个类的实例化延迟到其子类,通俗来说:它提供了一种实例化逻辑委托子类的方法。

代码示例:

定义NetworkConfigFactoryService工厂类

package com.universal.core.designPatterns.factoryMethod.factory;

import com.universal.core.designPatterns.factoryMethod.NetworkConfigCrudService;

public interface NetworkConfigFactoryService {

    /**
     * 获取指定的处理逻辑类
     * 
     * @param productType
     * @return
     */
    NetworkConfigCrudService getSpecificService(String productType);
}

NetworkConfigFactoryService工厂实现类

@Service
public class NetworkConfigFactoryServiceImpl implements NetworkConfigFactoryService {

    private final AServiceImpl aService;
    private final BServiceImpl bService;
    private final CServiceImpl cService;
    private final DServiceImpl dService;

    @Override
    public NetworkConfigCrudService getSpecificService(String productType) {
        NetworkConfigCrudService networkConfigCrudService = null;
        switch (productType){
            case "A" :
                networkConfigCrudService = aService;
                break;
            case "B":
                networkConfigCrudService = bService;
                break;
            case "C":
                networkConfigCrudService = cService;
                break;
            case "D":
                networkConfigCrudService = dService;
                break;
        }
        return networkConfigCrudService;
    }
}

定义网点操作接口NetworkConfigCrudService

public interface NetworkConfigCrudService {

    NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO);
}

它的实现类分别是 AServiceImpl、BServiceImpl、CServiceImpl、DServiceImpl,分别对应不同的逻辑:

图片

image.png

@Service
public class AServiceImpl implements NetworkConfigCrudService {
    @Override
    public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
        return new NetworkConfigVO();
    }
}

@Service
public class BServiceImpl implements NetworkConfigCrudService {
    @Override
    public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
        return new NetworkConfigVO();
    }
}
@Service
public class CServiceImpl implements NetworkConfigCrudService {
    @Override
    public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
        return new NetworkConfigVO();
    }
}
@Service
public class DServiceImpl implements NetworkConfigCrudService {
    @Override
    public NetworkConfigVO getNetwork(NetworkConfigDTO networkConfigDTO) {
        return new NetworkConfigVO();
    }
}

控制层NetworkConfigController

@RestController
@Slf4j
@RequestMapping(value = "/networkConfig")
public class NetworkConfigController {
    private final NetworkConfigFactoryService factoryService;

    @PostMapping(value = "/getNetworkDetails", produces = MediaType.APPLICATION_JSON_VALUE)
    public ApiResult<NetworkConfigVO> getNetworkDetails(@RequestBody NetworkConfigDTO networkConfigDTO) {
        //获取AService处理类逻辑
        NetworkConfigCrudService aService = factoryService.getSpecificService("A");
        NetworkConfigVO network = aService.getNetwork(networkConfigDTO);
        return ApiResult.success(network);
    }
}

6 观察者模式

观察者模式Observer 定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

*初识观察者模式:报社+订阅者 = 观察者模式。

要点

  • 观察者模式定义了对象之间一对多的关系。

  • 主题(可观察者)用一个共同的接口来更新观察者。

  • 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。

  • 使用次模式时,你可以从被观察者处推或拉数据(推的方式被认为是更正确的)。

  • 有多个观察者时,不可以依赖特定的通知次序。

  • java中有多种观察者模式的实现,包括了通用的java.util.Observable,不过需要注意Observable实现上所带来的问题,有必要的话,可以实现自己的Observable

  • Spring也大量使用观察者模,比如ListenrEvent消息订阅与发布;

案例

直接以气象站为例,其中天气信息就表示被观察者,天气布告板就表示订阅者和观察者,当天气发生变化(被观察者)时,会通过notifyObserver通知所有观察者,并调用他们的控制方法处理数据。

一个WeatherData对象负责追踪目前的天气状况(温度,湿度,气压)。我们希望你们能建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。

气象监测应用的对象分析

此系统中的三个部分是:

  • 气象站(获取实际气象数据的物理装置)

  • WeatherData对象(最总来自气象站的数据,并更新布告板)

  • 布告板(显示目前天气状况给用户看)。

实现气象站

/主题接口
interface Subject{
    //注册观察者
    public void registerObserver(Observer o);
    //删除观察者
    public void removeObserver(Observer o);
    //当主题状态改变时,这个方法会被调用,以通知所有的观察者
    public void notifyObserver();
}

interface Observer {
    //当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者
    public void update(float temp,float humidity,float pressure);
}

interface Display {
    //当布告板需要显示时,调用此方法
    public void display();
}

在WeatherData中实现主题接口

class WeatherData implements Subject{

    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observers=new ArrayList<Observer>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i=observers.indexOf(o);
        if(i>=0){
            observers.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for(Observer observer:observers){
            observer.update(temperature,humidity,pressure);
        }
    }
    //当从气象站得到更新观测值时,我们通知观察者
    public void measurementsChanged(){
        notifyObserver();
    }

    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
    //WeatherData的其他方法
}

建立布告板

其中的一个布告板:

class CurrentConditionDisplay implements Observer, DisplayElement {
    // 温度
    private float temperature;
    // 湿度
    private float humidity;
    // 气压
    private float pressure;

    private Subject weatherData;

    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void display() {
        System.out.println("这里气象台更新的天气数据...");
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure
        display();
    }
}

利用内置的支持重写WeatherData

class WeatherDataTWO extends Observable{
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherDataTWO(){

    }

    public void measurementsChanged(){
        //在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变
        setChanged();
        //我们没有调用notifyObservers传送数据对象,表示我们采用的做法是拉。
        notifyObservers();
    }

    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

利用内置观察者重写布告板

class CurrentConditionsDisplay implements java.util.Observer,DisplayElement{

    Observable observable;
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("这里气象台更新的天气数据...");
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof  WeatherDataTWO){
            WeatherDataTWO weatherDataTWO= (WeatherDataTWO) o;
            this.temperature=weatherDataTWO.getTemperature();
            this.humidity=weatherDataTWO.getHumidity();
            display();
        }
    }
}

7 模板方法模式

模板方法(Template Method) 是一种行为设计模式。模板方法设计模式用于创建方法存根并将某些实现步骤推迟到子类。

模板方法定义了执行算法的步骤,它可以提供可能对所有或者部分子类通用的默认实现,下面通过一个简单的例子来理解这个模式,假设我们想提供一种算法了该房子,建造房屋需要执行的步骤是:建造地基->建造支柱->建造墙壁和窗户。重点的一点是我们不能改变执行的顺序,因为我们不能在构建基础之前构建窗口,所以在这种情况下,我们可以创建一个模板方法,它将使用不同的方法来建造房子,现在盖房子的地基对于所有类型的房子都是一样的,无论是木房、玻璃房子还是混泥土房。所以我们可以为此提供基础实现,如果子类想要覆盖这个方法,他们可以自己选择,但是大多数情况下,所有类型的房屋都很常见。为了确保子类不覆盖模板方法,我们应该将其设为最终方法。

模板方法抽象类

由于我们希望某些方法由子类实现,因此我们必须将我们的基类设为抽象类。

定义抽象类HouseTemplate

public abstract class HouseTemplate {

    /**
     * buildHouse()是模板方法,定义个执行几个步骤的执行顺序
     *
     * template method, final so subclasses can't override final修饰,子类不能重写
     */
    public final void buildHouse(){
        //建造地基
        buildFoundation();
        //建造柱子
        buildPillars();
        //建造墙壁
        buildWalls();
        //建造窗户
        buildWindows();
        System.out.println("House is built successfully");
    }

    private void buildFoundation() {
        System.out.println("Building foundation with cement, iron rods and sand");
    }

    /**
     * methods to be implemented by subclasses
     */
    public abstract void buildPillars();
    public abstract void buildWalls();

    /**
     * default implementation
     */
    private void buildWindows() {
        System.out.println("Building Glass Windows");
    }


}

WoodenHouse

package com.universal.core.designPatterns.templateMethod;

/**
 * 木房
 */
public class WoodenHouse extends HouseTemplate {
    @Override
    public void buildPillars() {
        System.out.println("Building Pillars With Wood coating...");
    }

    @Override
    public void buildWalls() {
        System.out.println("Building Wooden Walls...");
    }
}

GlassHouse

package com.universal.core.designPatterns.templateMethod;

/**
 * 玻璃房
 */
public class GlassHouse extends HouseTemplate {

    @Override
    public void buildPillars() {
        System.out.println("Building Pillars With Glass coating...");
    }

    @Override
    public void buildWalls() {
        System.out.println("Building Glass Walls...");

    }
}

ConcreteHouse

package com.universal.core.designPatterns.templateMethod;

/**
 * 混泥土房屋
 */
public class ConcreteHouse extends HouseTemplate {
    @Override
    public void buildPillars() {
        System.out.println("Building Pillars With Concrete coating...");
    }

    @Override
    public void buildWalls() {
        System.out.println("Building Concrete Walls...");

    }
}

HousingClient

package com.universal.core.designPatterns.templateMethod;

public class HousingClient {

    public static void main(String[] args) {
        HouseTemplate houseBuilder = new WoodenHouse();
        houseBuilder.buildHouse();

        System.out.println("--------------");

        houseBuilder = new GlassHouse();
        houseBuilder.buildHouse();

        System.out.println("--------------");
        houseBuilder = new ConcreteHouse();
        houseBuilder.buildHouse();

    }
}

输出结果:

Building foundation with cement,iron rods and sand
Building Pillars With Wood coating...
Building Wooden Walls...
Building Glass Windows
House is built successfully
--------------
Building foundation with cement,iron rods and sand
Building Pillars With Glass coating...
Building Glass Walls...
Building Glass Windows
House is built successfully
--------------
Building foundation with cement,iron rods and sand
Building Pillars With Concrete coating...
Building Concrete Walls...
Building Glass Windows
House is built successfully

Process finished with exit code 0

示例二

下面是一个简单的咖啡店制作咖啡的例子,展示了模板模式的应用。在这个例子中,我们有一个抽象的`CoffeeMaker`类,它定义了制作咖啡的基本步骤(模板方法),而具体的咖啡种类(如美式咖啡`Americano`、卡布奇诺`Cappuccino`)则作为子类,根据需要重写某些步骤。

### 抽象类:CoffeeMaker(模板)

public abstract class CoffeeMaker {
    // 模板方法,定义了制作咖啡的流程
    public final void brewCoffee() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
        System.out.println("Coffee is ready!");
    }

    // 基本方法:烧水
    protected void boilWater() {
        System.out.println("Boiling water");
    }

    // 基本方法:冲泡咖啡,留给子类实现
    protected abstract void brew();

    // 基本方法:倒进杯子
    protected void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 基本方法:添加调料,子类可以选择是否重写
    protected void addCondiments() {
        System.out.println("Adding default condiments");
    }

    // 钩子方法,子类可以根据需要决定是否添加调料
    protected boolean customerWantsCondiments() {
        return true;
    }
}

### 具体实现类:Americano(具体模板)

public class Americano extends CoffeeMaker {

    @Override

    protected void brew() {

        System.out.println("Brewing Americano");

    }



    // 美式咖啡默认添加少量调料,但这里我们不重写customerWantsCondiments(),所以使用父类的默认实现

}

```

### 具体实现类:Cappuccino(具体模板)

public class Cappuccino extends CoffeeMaker {

    @Override

    protected void brew() {

        System.out.println("Brewing Cappuccino");

    }



    @Override

    protected void addCondiments() {

        System.out.println("Adding milk and chocolate");

    }



    // 可以选择重写customerWantsCondiments()来改变默认行为,但这里我们保持默认

}

```

### 客户端代码

public class CoffeeShop {

    public static void main(String[] args) {

        CoffeeMaker coffeeMaker = new Americano();

        coffeeMaker.brewCoffee();



        System.out.println("\n--- Making another coffee ---");



        coffeeMaker = new Cappuccino();

        coffeeMaker.brewCoffee();

    }

}


在这个例子中,`CoffeeMaker`类定义了制作咖啡的一系列步骤,其中`brew()`方法是抽象的,必须由子类实现,而`addCondiments()`方法虽然给出了默认实现,但也允许子类重写以适应不同种类咖啡的需求。这样,通过模板方法模式,我们既能保持咖啡制作流程的一致性,又能够灵活地制作出不同类型的咖啡。

8 适配器模式

适配器模式Adapter 是将一个接口转换成另一个客户所期望的接口。**Adapter** 适配器让那些本来因为接口不兼容的类可以合作无间。

适配器模式中的角色分析

  • 目标接口(Traget):客户期待的接口,目标可以是具体的或者抽象的类,也可以是接口。

  • 需要适配的对象(Source Adaptee):需要适配的对象。

  • 适配器(Adapter):通过包装一个需要适配的对象,把原接口转成目标接口。

举个例子:我们以网线上网为例,现在有一根水晶头网线,但是它的接口与电脑的不匹配(因为电脑的是usb或者typec),那么就需要一个转接头,也就是我们说的适配器,才能够上网,下面的转接头可以理解为适配器:网卡转接头、VGA转接头、HDMI转接头

类适配器

首先我们拥有一根网线, 他有上网的功能,但是它的接口与电脑不匹配:

//要适配的类:网线
public class Adaptee {
    //功能:上网
    public void request(){
        System.out.println("链接网线上网");
    }
}

因此我们定义了一个usb接口,也就是上面提到的目标接口(Target):

//接口转换器的抽象实现
public interface NetToUsb {
    
    //作用:处理请求,网线 => usb
    public void handleRequest();
}

定义一个适配器继承着网线,连接着usb接口:

//真正的适配器,余姚链接usb,连接网线
public class Adapter extends Adaptee implements NetToUsb {
    
    @Override
    public void handleRequest() {
        //可以上网了
        super.request();
    }
}

上网的具体实现:

//客户端类:想上网,插不上网线
public class Computer {
    //电脑需要连接上转接器才可以上网
    public void net(NetToUsb adapter){
        //上网的具体实现:找一个转接头
        adapter.handleRequest();
    }

    public static void main(String[] args) {
        //电脑,适配器,网线
        Computer computer = new Computer(); //电脑
        Adapter adapter = new Adapter();    //转接器
        computer.net(adapter);              //电脑直接连接转接器就可以
    }
}

对象适配器应用

  • java.util.Arrays#asList()open in new window

  • java.util.Collections#list()open in new window

  • java.util.Collections#enumeration()open in new window

  • javax.xml.bind.annotation.adapters.XMLAdapter

总结

设计模式(Design pattern) 代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。对于一个进阶高级开发的技术人员来说,了解设计模式的理念和具体的实现颇为重要,本期内容分享就到这里了,希望对你能有所帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-无-为-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值