设计模式:开篇--体验设计

简介: 试想一下,如果写出来的代码如艺术品,那将是多么美妙的一件事。因此,小编该好好学习一下怎么去设计代码了。设计模式这种东西来到世界上已经有相当历史了,可是工作了之后发现真正能用上的还是少数人。

试想一下,如果写出来的代码如艺术品,那将是多么美妙的一件事。因此,小编该好好学习一下怎么去设计代码了。设计模式这种东西来到世界上已经有相当历史了,可是工作了之后发现真正能用上的还是少数人。在写代码之前认真思考并设计一下,能省去相当一部分维护代码的成本。朋友推荐了《Head First 设计模式 (中文版)》这本书。小编就直接以这本书来打开设计的大门了。在设计之前我们体会一下根据需求的变化,没有设计的代码会让我们手忙脚乱



1:需求来了-使用继承

  • 需求:SimUDuck这款游戏会出现各种鸭子,游泳,呱呱叫
  • 考虑:拓展的是多个鸭子,而叫和游泳每个鸭子都有,会有长的不一样的鸭子
  • 做法:定义鸭子父类Duck,定义三个方法,quack、swim、display。外观不一样,那么抽象display。其他实现了。增加鸭子只需要继承Duck并实现不同的外观就行了。
public abstract class Duck {

    public void quack() {
        System.out.println("呱呱叫");
    }

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();
}

public class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观:绿头");
    }
}

public class RedheadDuck extends Duck {

    @Override
    public void display() {
        System.out.println("外观:红头");
    }
}

img_a052fcb2c937ae6f0f42fb110bb59dcf.png
Demo-1

总结:感觉还是很可以的,感觉以后增加鸭子只要多写个鸭子类就好了。

2:增加需求-拓展功能

  • 需求:让鸭子会飞
  • 考虑:难不倒我嘛,只要在Duck实现一个fly的方法下马所有的鸭子就成功的会飞了
  • 做法:Duck增加fly的方法

代码仅仅修改了Duck类

public abstract class Duck {

    public void quack() {
        System.out.println("呱呱叫");
    }

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();

    public void fly() {
        System.out.println("飞");
    }
}

img_a4768caa9f1bde930cbf014a521266d5.png
Demo-2

总结:内心觉得当初设计还是有点用的,完美适应增加的需求

3:再增加需求-定制功能的实体

  • 需求:加橡皮鸭和诱饵鸭,橡皮鸭的会吱吱叫,不会飞。诱饵鸭不会叫也不会飞
  • 考虑:父类定义的某些方法的实现,这时候字类不需要了,覆盖掉就好了
  • 做法:继承Duck增加两个鸭子RubberDuck和DecoyDuck。覆盖需要重新实现的方法。
public class RubberDuck extends Duck {


    /**
     * 覆盖父类方法,实现新的功能
     */
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }

    @Override
    public void display() {
        System.out.println("外观:橡皮鸭");
    }

    @Override
    public void fly() {
        //不会飞
    }
}

  public class DecoyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观:诱饵鸭");
    }

    @Override
    public void quack() {
        // 不会叫
    }

    @Override
    public void fly() {
        // 不会飞
    }
}
img_edb7cd1d24afaa724041a0a164bdadc7.png
Demo-3

总结:这时候随着需求不断增加,感觉当初采用继承的方式去设计代码有点不够用了

  • 问题:以后所有的鸭子都默认有父类的方法,如果不需要这些方法,就得每个都提供空实现,如果忘记了提供,那么就会多出不需要的方法了。代码不优雅,看着都难受。

4、优化设计-使用接口

  • 需求:改变一下设计,引入接口
  • 考虑:变化的方法有fly和quack。
  • 做法:抽取fly到Flyable接口,抽取quack到Quackable接口,需要的子类自行实现
public interface Flyable {
    void fly();
}
public interface Quackable {
    void quack();
}
public abstract class Duck {

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();

}
public class RedheadDuck extends Duck implements Flyable, Quackable {

    @Override
    public void display() {
        System.out.println("外观:红头");
    }

    @Override
    public void fly() {
        System.out.println("飞");
    }

    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}
public class MallardDuck extends Duck implements Flyable, Quackable {
    @Override
    public void display() {
        System.out.println("外观:绿头");
    }

    @Override
    public void fly() {
        System.out.println("飞");
    }

    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}

public class RubberDuck extends Duck implements Quackable {

    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }

    @Override
    public void display() {
        System.out.println("外观:橡皮鸭");
    }
}

public class DecoyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观:诱饵鸭");
    }
}
img_f33e414c1d0f3640cd3be50686717b3b.png
Demo-4

总结:使用接口之后发现确实可以不写这么多空实现了,但是也暴露出了另一个问题,

  • 问题:会出现重复的代码,试想一下,如果很多个鸭子都会同样的飞,那么每个鸭子都要去实现同样的fly方法。因此简单的使用接口也不太好。

5:进一步优化设计-使用组合

  • 需求:拓展的时候要解决重复代码和动态实现方法的问题
  • 考虑:继承会使方法不灵活,接口会导致重复代码。尝试组合一下,并面向接口编程
  • 做法:封装鸭子的行为(动态的方法),定义行为接口。定义行为类去实现行为接口(面向接口)。在Duck父类中组合这些行为。而字类提供具体的行为实现。

行为:FlyBehavior以及他的不同实现方式:飞,不会飞...

public interface FlyBehavior {
    void fly();
}
public class FlyNoWay implements FlyBehavior {

    @Override
    public void fly() {
        //不会飞
    }
}
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("飞");
    }
}

行为:QuackBehavior 以及他的不同实现方式:呱呱叫,吱吱叫,不会叫...

public interface QuackBehavior {
    void quack();
}

public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        //什么都不做,不会叫
    }
}

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

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}

实体鸭子:实体鸭子组合行为,字类实例化具体行为

public abstract class Duck {


    FlyBehavior flyBehavior;

    QuackBehavior quackBehavior;

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();

    public void performQuack() {
        quackBehavior.quack();
    }

    public void performFly() {
        flyBehavior.fly();
    }

}
public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("外观:绿头");
    }


}

public class RedheadDuck extends Duck {


    public RedheadDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("外观:红头");
    }


}
public class RubberDuck extends Duck {

    public RubberDuck() {
        quackBehavior = new Squeak();
        flyBehavior = new FlyNoWay();
    }

    @Override
    public void display() {
        System.out.println("外观:橡皮鸭");
    }


}
public class DecoyDuck extends Duck {

    public DecoyDuck() {
        quackBehavior = new MuteQuack();
        flyBehavior = new FlyNoWay();
    }

    @Override
    public void display() {
        System.out.println("外观:诱饵鸭");
    }

}
img_0a6dc5ed99657d5b03cba0ef3c0959de.png
Demo-5

总结:光从uml上看就能体会到组合的拓展性是多么的强了。重构成这样之后,不用更担心拓展任何的功能的鸭子了,如果没有功能,你就提供接口,并提供实现类,组合到新的鸭子里即可。那么你可能你会说了,这样还是不够动态。如果我要连行为都是动态的呢?

6:增加需求-轻松拓展

需求:增加一个模型鸭,不会飞,会呱呱叫。但是模型鸭可以变成吱吱叫,并且会用火箭飞
考虑:由于我们将行为当成属性组合到父类里面了,要改变行为,那么提供改变属性的set方法即可。
做法:修改Duck类,增加改变属性flyBehavior、quackBehavior的方法。拓展模型鸭提供默认行为实现。增加fly的实现方式用火箭飞

public class FlyRocketPowered implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("用火箭飞");
    }
}

public abstract class Duck {


    FlyBehavior flyBehavior;

    QuackBehavior quackBehavior;

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();


    public void performQuack() {
        quackBehavior.quack();
    }

    public void performFly() {
        flyBehavior.fly();
    }

    /**
     * 修改行为:FlyBehavior
     *
     * @param flyBehavior
     * @return
     */
    public Duck setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
        return this;
    }

    /**
     * 修改行为:QuackBehavior
     *
     * @param quackBehavior
     * @return
     */
    public Duck setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
        return this;
    }
}

public class ModelDuck extends Duck {

    public ModelDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyNoWay();
    }

    @Override
    public void display() {
        System.out.println("外观:模型鸭");
    }
}

public class Test {
    public static void main(String[] args) {
        Duck modelDuck = new ModelDuck();
        modelDuck.performQuack();
        modelDuck.performFly();

        modelDuck.setQuackBehavior(new Squeak());
        modelDuck.setFlyBehavior(new FlyRocketPowered());

        modelDuck.performFly();
    }
}
img_466e9cc2d050886949fb74e01f65ac52.png
Demo-6

总结:从最初的继承,到接口,到封装改变,面向接口变成。我们已经体会到了设计的美妙。我们发现了这样写出来的代码具有较高的拓展性了。无论是拓展行为,还是拓展实体。都可以驾驭的了。但是如果一开始没有思考和设计,随意设计,一旦项目的代码和业务多起来,那么重构的成本将会提高很多,因此写代码之前花点时间思考一下拓展性是很有必要的。

总结一下这个过程当中的一些设计原则。

  • 将变化的部分和固定部分的区别开来,封装变化
  • 面向接口编程,而不是面向实现编程
  • 组合比继承好用,多用组合,少用继承

参考文献:[1]


  1. 《Head First 设计模式 (中文版)》

目录
相关文章
|
设计模式 算法 关系型数据库
设计模式 | 开篇简介
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
76 1
设计模式 | 开篇简介
|
算法 设计模式
设计模式——开篇
什么是设计模式? 一说起设计模式,可能很多人都觉得很高大上的感觉,事实上,设计模式只是针对某一类问题的最佳解决方案而已,设计模式是由许多优秀的软件系统中总结出来的可成功复用的设计方案。
971 0
|
uml
设计模式:开篇
  最近在整理设计模式这个系列,这里做一下全局的概括。本系列的文章对于初识设计模式的朋友也许不太适应,对于那些了解过或者使用过设计模式的人比较适应,本系列的文章对设计模式的关键点做了一个终结性的陈述,也列举了相关例子方便理解和记忆,但是并没有循序渐进的讲解。
1012 0
|
程序员
[转载].NET设计模式(1):开篇
.NET设计模式开篇 ——.NET设计模式系列之一 Terrylee,2005年12月06日 前言 加入Design & Pattern团队有几个月的时间了,惭愧的是从没有写过关于设计模式的随笔,得到wayfarer的同意,把企业库系列的随笔放在了团队的首页上。
1165 0
|
16天前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
19 0
|
1月前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
37 0
|
3月前
|
设计模式 存储 算法
Java 设计模式最佳实践:三、行为模式
Java 设计模式最佳实践:三、行为模式
22 0
|
2月前
|
设计模式 前端开发 JavaScript
观察者模式 vs 发布-订阅模式:两种设计模式的对决!
欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚开始学习前端的读者们打造的。无论你是初学者还是有一些基础的开发者,我们都会在这里为你提供一个系统而又亲切的学习平台。我们以问答形式更新,为大家呈现精选的前端知识点和最佳实践。通过深入浅出的解释概念,并提供实际案例和练习,让你逐步建立起一个扎实的基础。无论是HTML、CSS、JavaScript还是最新的前端框架和工具,我们都将为你提供丰富的内容和实用技巧,帮助你更好地理解并运用前端开发中的各种技术。