zoukankan      html  css  js  c++  java
  • 设计模式之欢迎来到设计模式世界(一)

    亲爱的朋友,欢迎你来到对象村,开始走进设计模式的世界。这里的每个人都很熟练的使用设计模式,很快我和你们一起,都会学习的很好,通过设计模式,跻身上流社会。

    计划每一章节的学习,通过几个篇幅来完成,理论+实践的方式。书中很多地方用到了图形表示,小编尽量用图文的方式和大家互动。先用理论建立知识,再用图形象地描述巩固学习。每篇文章给出书中的思考题和大家互动,在后文给出答案。力争让没读过此书的朋友也能有个理解。小编第一次尝试书本跟读,希望大家多给意见,同大家一起进步。

    记得大学的时候,C++的老师在教我们面向对象的是就是用的鸭子会飞、会叫的例子,没想到《Head First 设计模式》里用的也是这个,没看过太多的书,可能鸭子比较特殊吧。前几个月,还出现各种鸭子的表情包,还有很出名的小黄鸭,让我不禁赞叹,鸭子才是人类比较好的朋友呀。

    那么第一个问题来了,一家公司想要设计一款关于鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。游戏的模型已经有了面向对象的思想,设计了一个鸭子的超类(Superclass),并让各种鸭子继承这个超类。现在想让鸭子会飞,你会怎么做呢?初始的鸭超类如下,随着我们一步步学习,我们将这个超类一步步优化,敬请期待。

    public class Duck {
        public Duck() {
        
        }
        
        // 游泳
        public void swim() {
            System.out.println("I can swim");
        }
        
        // 呱呱叫
        public void quack() {
            
        }
        
        // 显示
        public void display() {
            
        }
        
        // 飞行
        public void fly() {
            
        }
    }
    

    我第一个冒出来的想法就是继承,我也算是写了几年程序的老鸟了,没想到也只能想到这么low的想法,用继承来解决问题。在继承中加入fly()方法。好啦,现在所有的鸭子都会飞了,包括玩具鸭,天呐,这是什么情况,难道你还要给玩具鸭装发动机么。要知道,并不是所有的鸭子都会飞的呀。如果以后还有一只木头鸭,啥都不会,怎么办呢。或者说以后鸭子变异了,多了功能,也并不是所有的鸭子都需要有的功能呢。这就引出了继承的弊端,给大家留的第一个思考题,继承会有哪些缺点呢?期待在留言区看到你的答案(答案下期揭晓)。以下哪几个是缺点:

    • A 代码在多个子类中重复
    • B 运行时的行为不容易改变
    • C 我们不能让鸭子跳舞
    • D 很难知道所有鸭子的全部行为
    • E 鸭子不能同时又飞又叫
    • F 改变会牵一发动全身,造成其他鸭子不想要的改变

    所以,我们还是不能用继承来解决刚提出来的问题。有些鸭子会飞,有些鸭子会叫,有些啥都不会,鸭子行为太过于繁杂,不能一以贯之。这就引出了我们需要接触到的设计原则「找到应用中可能需要变化之处,并把它们独立出来,不要和那些不需要变化的代码混在一起」这样,代码变化引起的不经意后果变少,系统变得更有弹性。在刚才的例子中,我们就把鸭子会飞以及会叫的属性的分开,组建一个新类来表示。

    第二个重点来了,如何设计那组实现飞行和呱呱叫的行为的类呢。那就把鸭子的行为放在分开的类中,此类专门提供某行为接口的实现,这样鸭子类就不再需要知道行为的实现细节。鸭子的子类将实现接口,不会绑死在鸭子的子类中,做一些没必要的事情。这就是我们需要说的第二个设计原则「针对接口编程,而不是实现编程

    我们利用接口代表每个行为,在这里用FlyBehavior与QuackBehavior分别表示飞行行为和呱呱叫行为,并且行为的每个实现都将实现其中的一个接口。这样的好处就是飞行和呱呱叫的动作可以被其他对象复用,复用的同时这些行为彻底和鸭子类无关。前文说到的,鸭子还想增加其他的行为,也可以通过此方式来进行,既不会影响到既有的行为类,也不会影响到使用到飞行行为的鸭子类。整合之前鸭子的行为,在实践中就是把飞行和呱呱叫方法在之前的超类中删除,通过接口来实现,进行分离。
    飞行和呱呱叫接口如下:

    // 飞行接口
    public interface FlyBehavior {
        public void fly();
    }
    
    // 呱呱叫接口
    public interface QuackBehavior {
        public void quack();
    }
    

    相应的实现类如下:

    // 飞行类
    public class FlyWithWings implements FlyBehavior {
    
    	@Override
    	public void fly() {
        	// 实现鸭子的飞行
            System.out.println("I'm flying!!!");
    	}
    
    }
    
    // 呱呱叫类
    public class Quack implements QuackBehavior {
    
    	@Override
    	public void quack() {
        	// 实现鸭子的呱呱叫
    		System.out.println("Quack");
    	}
    
    }
    

    如果鸭子要动态设定行为呢?什么意思,就是说「模型鸭想飞,利用火箭动力飞」。在我们现有的设计中,难不倒我们。我们可以在鸭子子类中通过设定方法「setter method」的方式来设定鸭子的行为,而不用在构造器中实例化。构造器中模型鸭不会飞,通过设定鸭子的行为,把火箭动力行为设定好,相当于把鸭子加了一个bug,设定好之后再给鸭子去飞。

    // 在超类中添加动态设置的行为
    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
    
    // 通过一个测试类进行测试反馈
    public class MiniDuckSimulator {
    	public static void main(String[] args) {
    		Duck mallard = new MallardDuck();
    		// 这里调用MallardDuck继承来的performQuack(),进而委托给该对象的QuackBehavior对象处理,
    		// 也就是说,调用继承来的QuackBehavior的quack(),performFly同理
    		mallard.performQuack();
    		mallard.performFly();
    		
    		Duck model = new ModelDuck();
    		// 第一次调用,不会飞
    		model.performFly();
    		// 调用继承来的setter方法,把火箭动力飞行的行为定到模型鸭中,模型鸭能一飞冲天
    		model.setFlyBehavior(new FlyRocketPowered());
    		// 这样就成功的改变了行为
    		model.performFly();
    	}
    }
    
    // 运行结果,我们实现了火箭助力飞行
    Quack
    I'm flying!!!
    I can't fly
    I'm flying with a  rocket
    

    好了,现在我们有鸭子超类,鸭子类,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。之前继承的想法,就是IS-A(是一个)的关系,现在通过封装行为,实现接口,变成了HAS-A(有一个)的关系,将两个类结合起来,就成为了一个组合(composition)。区别显而易见,鸭子的行为是通过对象的「组合」来的,而不是「继承」而来。恭喜你,掌握了第三个设计原则「多用组合,少用继承」。使用组合建立系统具有很大的弹性,只要组合的行为对象符合正确的接口标准,那就可以「在运行时动态的改变行为」。

    简单的学到这里,其实已经在无形之中引出了一个设计模式「策略模式」,他定义了算法族,分别封装起来,让它们之间互相替换,此模式让算法的变化独立于使用算法的客户。有了这个策略模式,系统不需要担心遇到任何改变,通过上述的操作,就能灵活应对,神不神奇,意不意外,惊不惊喜。

    现在对之前的理论做个简单的总结:首先我们先用鸭子举个例子,引出超类Duck,子类的概念;第二,因为鸭子需要各自有行为,呱呱叫,飞行行为,我们设计出接口QuackBehavior和FlyBehavior,实现类FlyWithWings(实现鸭子飞行)等其他行为和Quack(实现鸭子呱呱叫等其他叫声);第三,我们需要鸭子有火箭般的速度,继而引出FlyRocketPowered的行为,方便鸭子动态设定。自此三个步骤,把今天的行为实现,得出了三个设计原则和一个设计模式。最终的Duck类如下

    public abstract class Duck {
    	// 为行为接口类型声明两个引用变量,所有 鸭子类都继承他们
    	FlyBehavior flyBehavior;
    	QuackBehavior quackBehavior;
    	
    	public Duck() {
    		
    	}
    	
    	public void setFlyBehavior(FlyBehavior fb) {
    		flyBehavior = fb;
    	}
    	
    	public void setQuackBehavior(QuackBehavior qb) {
    		quackBehavior = qb;
    	}
    	
    	abstract void display();
    	
    	public void performQuack() {
    		// 委托给呱呱叫行为类
    		quackBehavior.quack();
    	}
    	
    	public void performFly() {
    		// 委托给飞行行为类
    		flyBehavior.fly();
    	}
    	
    	public void swim() {
    		System.out.println("I can swim");
    	}
    }
    

    代码流程部分就到这里,在学习的过程当中,还有很多类图,很多思考以及总结等我去完善。这是第一篇,下面一篇会把这次的转变通过图文的方式做一个总结,小伙伴们先别急,学习是一个循序渐进的过程,小编会把代码写好,测试好,开源到GayHub上,同大家一起进步。

    今天的内容就到这里,欢迎各位拍砖!

    为了让小伙伴免去找资料的麻烦,公众号「奔跑吧攻城狮」回复设计模式,获取最经典的两本书籍《Head First设计模式》以及《大话设计模式》,一同开启新篇章。

  • 相关阅读:
    vue事件处理器--v-on
    vue循环-- v-for
    node-Socket编程
    JsonWebToken
    Mongodb 数据库
    Nodejs库-EXPRESS
    yarn和npm的区别
    Vue学习笔记【22】——Vue中的动画(列表的排序过渡)
    Vue学习笔记【21】——Vue中的动画(v-for 的列表过渡)
    Vue学习笔记【20】——Vue中的动画(使用动画钩子函数)
  • 原文地址:https://www.cnblogs.com/dimple91/p/10567893.html
Copyright © 2011-2022 走看看