• 设计模式之模板方法模式(封装算法)


    第一次设计

    下面,我们的学习将从咖啡和茶的制作上开始进行。
    泡咖啡的步骤:

    • 把水煮沸
    • 用沸水冲泡咖啡
    • 把咖啡倒入杯子
    • 加糖和牛奶

    泡茶的步骤:

    • 把水煮沸
    • 用沸水侵泡茶叶
    • 把茶倒入杯子
    • 加柠檬

    下面,用代码来实现上面的步骤:
    咖啡的实现:

    public class Coffee
        {
            void prepareRecipe()
            {
                boilWater();
                brewCoffeeGrinds();
                pourInCup();
                addSugarAndMilk();
            }
    
            public void boilWater()
            {
                Console.WriteLine("Boiling water");
            }
    
            public void brewCoffeeGrinds()
            {
                Console.WriteLine("Dripping Coffee through filter");
            }
    
            public void pourInCup()
            {
                Console.WriteLine("Pouring into cup");
            }
    
            public void addSugarAndMilk()
            {
                Console.WriteLine("Adding Sugar and milk");
            }
        }
    

    茶的实现:

    public class Tea
        {
            void prepareRecipe()
            {
                boilWater();
                brewTeaBag();
                addLemon();
                pourInCup();
            }
    
            public void boilWater()
            {
                Console.WriteLine("Boiling water");
            }
    
            public void brewTeaBag()
            {
                Console.WriteLine("Steeping the tea");
            }
            public void pourInCup()
            {
                Console.WriteLine("pouring into cup");
            }
    
            public void addLemon()
            {
                Console.WriteLine("Adding Lemon");
            }
    
    
        }
    

    改进

    从上面的代码可以发现,有两个步骤我们是重复了的,我们可以把重复的部分抽取出来,放入到一个基类中。
    把boilWater()、pourInCup()提取出来放入基类,把prepareRecipe()定义成抽象方法。但这样定义,对于prepareRecipe()仍然需要实现两次,我们将boilWater()、pourInCup()泛化,使用brew()和addCondiments()来替代,并且在基类中,将其定义成抽象方法。这样prepareRecipe()不在抽象,而是在基类中实现。
    基类:

        public abstract class CaffeineBeverage
        {
            public void prepareRecipe()
            {
                boilWater();
                brew();
                addCondiments();
                pourInCup();
            }
    
            public void boilWater()
            {
                Console.WriteLine("Boiling water");
            }
    
            public void pourInCup()
            {
                Console.WriteLine("pouring into cup");
            }
    
            public abstract void brew();
    
            public abstract void addCondiments();
        }
    

    下面看一幅图,来看看我们现在都做了些什么:

    什么是模板方法

    模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现
    在上面的代码中,我们模板方法就是prepareRecipe(),原因:

    • 它是一个方法
    • 它用作一个算法模板,在这个例子中,算法是用来制作咖啡因饮料
    • 在这里模板中,算法内的每一个步骤都被一个方法代表了。

    定义模板方法模式

    模板方法模式:在一个方法定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤

    这个抽象类包含了模板方法。
    primitiveOperation1()、primitiveOperation2()模板方法所用到的操作的抽象版本,模板方法本身和这两个操作的具体实现之间被解耦。
    一个模板方法中可能许多具体类,这个具体类实现了抽象类的操作。

    钩子(对模板方法进行挂钩)

    钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。
    下面来看看代码怎么写:

        public abstract class CaffeineBeverageWithHook
        {
            void prepareRecipe()
            {
                boilWater();
                brew();
                poueInCup();
                if (customerWantsCondiments())
                {
                    addCondiments();
                }
            }
            public abstract void brew();
            public abstract void addCondiments();
            void boilWater()
            {
                Console.WriteLine("Boiling water");
            }
            void poueInCup()
            {
                Console.WriteLine("Pouring into cup");
            }
            bool customerWantsCondiments()//这就是一个钩子,子类可以覆盖(这个方法通常是空的实现)
            {
                return true;
            }
        }
    

    使用钩子

    让我们看看它在实际代码里面的应用
    子类继承:

        public class CoffeeWithHook : CaffeineBeverageWithHook
        {
            public override void addCondiments()
            {
               Console.WriteLine("Adding Sugar through filter");
            }
            public override void brew()
            {
               Console.WriteLine("Dripping Coffee through filter");
            }
            public override bool customerWantsCondiments()
            {
                Console.WriteLine("Adding Sugar through filter?Y/N");
                string s = Console.ReadLine();
                if (s=="y")
                {
                    return true;
                }
                return false;
            }
        }
    

    测试:

            static void Main(string[] args)
            {
                CoffeeWithHook coffeewithhool=new CoffeeWithHook();
                coffeewithhool.prepareRecipe();
                Console.ReadKey();
            }
    

    关于钩子

    什么时候使用钩子

    当算法的某个实现可选的时候,可以使用钩子。当算法的某个实现是必须的时候,使用抽象方法

    钩子的目的

    钩子可以让子类有能力为其基类做一些决定。

    好莱坞原则

    好莱坞原则:别调用我们,我们会调用你。
    好莱坞原则防止依赖腐败:当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边测组件,边侧组件又依赖高层组件,这样依赖腐败就发生了。
    在好莱坞原则下,我们允许底层组件将自己挂钩到系统上,但高层组件会决定什么时候调用这些底层组件。

    好莱坞原则应用

    我们在之前设计模板方法的时候,其实就用到了好莱坞原则:

    在上面图中,CaffeineBeverage就是我们的高层组件,它能够控制冲泡方法的算法,只有在需要子类实现某个方法是才调用,饮料的客户代码只依赖CaffeineBeverage的抽象,而不依赖具体的类。

    好莱坞原则和依赖倒置原则

    在这里我们看到好莱坞原则感觉和依赖倒置原则很像,都是用于解耦。
    依赖倒置原则让我们尽量避免使用具体类,而多使用抽象,它更加注重与在设计中避免依赖。
    好莱坞原则则是一种用在创建框架或组件上的一种技巧,好让底层组件能够被挂钩计算,而又不会让高层组件依赖底层组件,它是创建一个有弹性的设计,允许底层结构能够互相操作,而又防止太过于依赖。

    使用模板方法来排序

    使鸭子类继承至IComparable接口,之后使用sort方法进行排序

        public class Duck:IComparable
        {
            private string name;
            private int weight;
            public Duck(string name, int weight)
            {
                this.name = name;
                this.weight = weight;
            }
            public  string toString()
            {
                return name + "weighs" + weight;
            }
            public int CompareTo(object obj)//需要提供的实现
            {
                Duck otherDuck = (Duck) obj;
    
                if (this.weight<otherDuck.weight)
                {
                    return -1;
                }
                else if (this.weight==otherDuck.weight)
                {
                    return 0;
                }
                else
                {
                    return 1;
                }
            }
        }
    

    排序

            static void Main(string[] args)
            {
                List<Duck> ducks=new List<Duck>{new Duck("11",11),new Duck("2",2),new Duck("13",13),new Duck("7",7)};
                DisPlay(ducks);
                ducks.Sort();
                DisPlay(ducks);
                Console.ReadKey();
            }
    

    在上面的例子中,我们可以看到子类提供的实现排序方法的算法。

  • 相关阅读:
    hashcode与equals的关系,hashcode的作用
    Exception和Error的区别
    spring下的beanutils.copyProperties方法是深拷贝还是浅拷贝?可以实现深拷贝吗?
    AopContext.currentProxy()该用法的意义
    spring如何解决循环依赖
    springboot注解以及手动使用事务
    反射中,Class.forName和ClassLoader区别
    ThreadLocal真的会造成内存泄露?
    Vue 插槽(slot)详细介绍(对比版本变化,避免踩坑)
    MySQL统计近7天(两周、一个月等)数据,没有数据显示为0
  • 原文地址:https://www.cnblogs.com/Tan-sir/p/8310899.html
Copyright © 2020-2023  润新知