【优化】模版方法模式+枚举工厂模式优化

今天做kama网上模版方法模式的题目18-咖啡馆,共经历了三次修改。其中最后一次优化在枚举类中加入了工厂,用到函数式接口以及方法引入的相关知识。这里通过对这次实践做一下总结,深化一下对模版方法模式函数式接口以及方法引入的理解。

题目来源:https://kamacoder.com/problempage.php?pid=1087


改进过程

第一次实现

import java.util.Scanner;
abstract class AbstractCoffeeModel{
    
    // 注意用private的话,子类看不到
    // private String coffeeName;
    protected String coffeeName;
    
    public AbstractCoffeeModel(String coffeeName) {
        this.coffeeName = coffeeName;
    }
    
    public final void makeCoffee(){
        System.out.println("Making " + coffeeName  + ":");
        doGrind();
        doBrew();
        if (this.coffeeName.equals("Latte")) {
            doAddMilk();
        }
        doAddAondiments();
        System.out.println();
    }
    
    // 定义抽象方法
    protected abstract void doGrind();
    protected abstract void doBrew();
    protected abstract void doAddAondiments();
    // 美式不需要实现;
    protected abstract void doAddMilk();
    
    enum CoffeeType{
        
        Americano(1, "American Coffee"),
        Latte(2, "Latte")
        ;
        
        private int code;
        private String name;
        
        // 构造函数
        CoffeeType(int code, String name) {
            this.code = code;
            this.name = name;
        }
        
            // 获取code
        public int getCode() {
            return code;
        }
    
        // 获取name
        public String getName() {
            return name;
        }
        
        // 根据code获取对应的name
        public static String getNameByCode(int code) {
            for (CoffeeType type : CoffeeType.values()) {
                if (type.getCode() == code) {
                    return type.getName();
                }
            }
            return null; // 如果没有匹配到,则返回null或抛出异常
        }
    }
}
class ConcreteCoffeeModel extends AbstractCoffeeModel {
    
    // public ConcreteCoffeeModel(String coffeeName) {
    //     this.coffeeName = coffeeName;
    // }
    
    public ConcreteCoffeeModel(String coffeeName) {
        super(coffeeName);
    }
    
    @Override
    protected void doGrind() {
        System.out.println("Grinding coffee beans");
    }
    
    @Override
    protected void doBrew() {
        System.out.println("Brewing coffee");
    }
    
    @Override
    protected void doAddAondiments() {
        System.out.println("Adding condiments");
    }
    
    @Override
    protected void doAddMilk() {
        System.out.println("Adding milk");
    }
    
}
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            int num = scanner.nextInt();
            String coffeeName = AbstractCoffeeModel.CoffeeType.getNameByCode(num);
            ConcreteCoffeeModel coffeeModel = new ConcreteCoffeeModel(coffeeName);
            coffeeModel.makeCoffee();
        }
    }
}

第一次改进 – 模版方法模式

上面的实现,虽然实现了需求,但是将具体的条件逻辑(如判断咖啡种类的 if 语句)放在抽象类中是不符合模板方法模式原则的。这会导致在添加新类型的咖啡时,需要修改抽象模板类,从而违背了模板方法模式应有的开放封闭原则(Open-Closed Principle)。正确的模版方法实现应该是下面这样的:

import java.util.Scanner;
enum CoffeeType{
    Americano(1, "American Coffee"),
    Latte(2, "Latte")
    ;
        
    private int code;
    private String name;
        
    // 构造函数
    CoffeeType(int code, String name) {
        this.code = code;
        this.name = name;
    }
        
    // 获取code
    public int getCode() {
        return code;
    }
    
    // 获取name
    public String getName() {
        return name;
    }
}
abstract class AbstractCoffeeModel{
    
    // 注意用private的话,子类看不到
    // private String coffeeName;
    protected String coffeeName;
    
    public AbstractCoffeeModel(String coffeeName) {
        this.coffeeName = coffeeName;
    }
    
    public final void makeCoffee(){
        System.out.println("Making " + coffeeName  + ":");
        doGrind();
        doBrew();
        doAddAondiments();
        System.out.println();
    }
    
    // 定义抽象方法
    protected abstract void doGrind();
    protected abstract void doBrew();
    protected abstract void doAddAondiments();
}
class AmericanoCoffeeModel extends AbstractCoffeeModel {
    
    // public ConcreteCoffeeModel(String coffeeName) {
    //     this.coffeeName = coffeeName;
    // }
    
    public AmericanoCoffeeModel() {
        super(CoffeeType.Americano.getName());
    }
    
    @Override
    protected void doGrind() {
        System.out.println("Grinding coffee beans");
    }
    
    @Override
    protected void doBrew() {
        System.out.println("Brewing coffee");
    }
    
    @Override
    protected void doAddAondiments() {
        System.out.println("Adding condiments");
    }
    
}
class LatteCoffeeModel extends AbstractCoffeeModel {
    
    // public ConcreteCoffeeModel(String coffeeName) {
    //     this.coffeeName = coffeeName;
    // }
    
    public LatteCoffeeModel() {
        super(CoffeeType.Latte.getName());
    }
    
    @Override
    protected void doGrind() {
        System.out.println("Grinding coffee beans");
    }
    
    @Override
    protected void doBrew() {
        System.out.println("Brewing coffee");
    }
    
    @Override
    protected void doAddAondiments() {
        System.out.println("Adding milk");
        System.out.println("Adding condiments");
    }
    
}
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            int num = scanner.nextInt();
            if(num == CoffeeType.Americano.getCode()) {
                AmericanoCoffeeModel coffeeModel = new AmericanoCoffeeModel();
                coffeeModel.makeCoffee();
            } else if (num == CoffeeType.Latte.getCode()) {
                LatteCoffeeModel coffeeModel = new LatteCoffeeModel();
                coffeeModel.makeCoffee();
            }
        }
    }
}

这样,后续如果要新增咖啡种类,就只需要新增枚举值,创建新的继承自模版类的具体咖啡类即可。


第二次改进 — 枚举工厂方法

可以看到,在客户端代码中,仍然存在if-else语句,这是不太优雅的,可以在继续在枚举类中加入工厂方法,实现动态地创建具体实现类(美式还是拿铁)。即:

使用枚举工厂方法:将具体的咖啡创建逻辑封装在枚举中,使得 Main 类可以通过枚举创建具体的咖啡对象,增加新的咖啡类型时不需要修改 Main 类。

最终代码:

import java.util.Scanner;
enum CoffeeType{
        
    //AmericanoCoffeeModel::new 是一个方法引用,指向 AmericanoCoffeeModel 的构造函数。这意味着当调用 factory.create() 时,它会使用 AmericanoCoffeeModel 的构造函数创建一个新的实例。
    Americano(1, "American Coffee", AmericanoCoffeeModel::new),
    Latte(2, "Latte", LatteCoffeeModel::new)
    ;
        
    private int code;
    private String name;
    private CoffeeModelFactory factory;
        
    // 构造函数
    CoffeeType(int code, String name, CoffeeModelFactory factory) {
        this.code = code;
        this.name = name;
        this.factory = factory;
    }   
        
    public int getCode() {
        return code;
    }
    
    public String getName() {
        return name;
    }
    
    public static CoffeeType getByCode(int code) {
        for (CoffeeType type : CoffeeType.values()) {
            if (type.getCode() == code) {
                return type;
            }
        }
        return null;
    }
    
    /**
     * 使用工厂方法创建具体的咖啡模型实例
     * 
     * @return 具体的咖啡模型实例
     */
    public AbstractCoffeeModel createCoffeeModel() {
        return factory.create();
    }
    
    
    /**
     * 创建具体咖啡模型实例的工厂接口
     */
    @FunctionalInterface  // 函数式方法,只能拥有一个抽象方法。
    private interface CoffeeModelFactory {
        AbstractCoffeeModel create();
    }
}
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            int num = scanner.nextInt();
            CoffeeType.getByCode(num).createCoffeeModel().makeCoffee();
        }
    }
}

接下来,详细解释一下这个枚举类:

1. 首先,创建一个接口 CoffeeModelFactory,用于生成 AbstractCoffeeModel 的实例。因为 CoffeeModelFactory 只有一个抽象方法 create(),它符合函数式接口的要求,可以被用作方法引用的目标。

     /**
     * 创建具体咖啡模型实例的工厂接口
     */
    @FunctionalInterface  // 函数式方法,只能拥有一个抽象方法。
    private interface CoffeeModelFactory {
        AbstractCoffeeModel create();
    }
  • @FunctionalInterface 注解:标记这个接口是一个函数式接口,确保接口中只有一个抽象方法。这个注解并非必需,但它可以帮助编译器检查接口是否符合函数式接口的要求。

2. 提供工厂生产咖啡模型实例的方法,调用工厂的create()方法。

     /**
     * 使用工厂方法创建具体的咖啡模型实例
     * 
     * @return 具体的咖啡模型实例
     */
    public AbstractCoffeeModel createCoffeeModel() {
        return factory.create();
    }

3. 使用方法引用创建工厂实例:在 CoffeeType 枚举中,通过方法引用(ClassName::new)将 AmericanoCoffeeModelLatteCoffeeModel 的构造函数分别绑定到 CoffeeModelFactory 接口的 create() 方法的实现上。这样,当我们需要创建相应的咖啡模型实例时,就可以通过 createCoffeeModel() 方法获取。

  • ClassName::new 这种方法引用用于调用 ClassName 的构造函数。它表示创建 ClassName 类型的新实例。
    Americano(1, "American Coffee", AmericanoCoffeeModel::new),
    Latte(2, "Latte", LatteCoffeeModel::new)
    ;

总结

1 模版方法模式

要记住模版方法模式优化后,不应该在抽象模版类中进行判断。对于要求不同的对象(美式或拿铁),算法的框架(模版方法)是一致的,创建不同的具体继承类来实现不同对象的差异化要求。

开闭原则

系统对扩展开放,对修改关闭。新增功能时,通过添加新子类即可,不需要修改已有代码。

这样以后增加新的对象(比如卡布奇诺)时,就不需要改变模版方法,只需要增加新的具体继承类即可。这样才遵循设计模式的开闭原则

2 函数式接口和方法引入

【bilibili】Java语法中的方法引用::是什么?

https://www.bilibili.com/video/BV1je4y1c79s?vd_source=dd1bad3ca79b4f5adadcfe93c604c7b5

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇