「设计原则 一」

「设计原则 」

一、开闭原则

顾名思义,在软件设计中应当遵循对扩展开放,而对修改关闭。也即在实际开发过程中,当需求变动业务调整时,在不改动源码的情况下可以扩展以支撑新的功能;这也要求了在设计之初制定技术方案时应有前瞻性。

  • 遵循开闭原则的好处:提高代码的复用性、可维护性、有利于单元测试。
  • 实现:在面向对象的设计中,通常可以通过定义接口或者抽象类来约束相同属性或者一般通用的实现(抽象),这样具体派生实现类可以将具体的实现封装在内部。即使业务变化,我们只需要相应的派生出一个实现类就可以实现扩展。不过在实际中,这种对业务的抽象能力要求还是比较高的。如果抽象的粒度太小,那么会伴随着繁杂的实现类;如果粒度太大却不利于扩展。经验的积累与思考很重要。
1.实际问题

商品价格变动模拟,如打折促销、涨价等

  • 定义顶层的商品接口(仅仅包含ID名称价格)
public interface Product {
  long getId();

  long getPrice();

  String getName();
}
  • 新建水果中香蕉的实现类
public class Banana implements Product {
    private  long id;
    private  long price;
    private  String name;

    public Banana(long id, long price, String name) {
        this.id = id;
        this.price = price;
        this.name = name;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public long getId() {
        return this.id;
    }

    @Override
    public long getPrice() {
        return this.price;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
  • 香蕉不易保存的特性决定了,如果库存较多只能打折进行处理。

如果直接修改Banana实现类中价格getPrice()势必会对其他的地方的调用产生影响,违背了开闭原则。因此增加BananaDiscountImp折扣类,当然这其实也是不合理的,仅仅作为举例,如果都是这种,会增加很多不必要的实现类,使得项目膨胀冗杂。

public class BananaDiscountImp extends Banana {

    public BananaDiscountImp(long id, long price, String name) {
        super(id, price, name);
    }

    /**
     * 原始价格
     */
    @Override
    public long getPrice() {
        return super.getPrice();
    }

    /**
     * 折后价格
     * (需借助BigDecimal转换,包括保留小数位等,80相当于8折)
     */
    public long getOriginalPrice() {
        return getPrice() * 80L;
    }
}
二、里氏替换原则
  • 含义:通俗的讲在继承过程中子类可以对基类的功能进行扩展,但不能改变基类原有的功能。在面向对象的程序设计中,继承作为三大特性之一。虽然带来了很大的便利性,但同时也增加了耦合性,侵入性。
  • 里氏替换原则实际上更是对继承过程中的一种规范与约束:1.子类可以增加自身特有的方法;2.子类可以实现基类的抽象方法,但不能覆盖基类的非抽象方法;3.当子类重载基类的方法时,方法的入参应该比基类更宽松;4.当子类实现基类的抽象方法时,方法的返回值应该比基类更严格;5.如果子类必须重写基类的方法时,应该考虑替换当前的继承关系,同时继承更加一般的基类,或者使用组合、聚合、依赖等其他方式替代。
1.实际问题

比较经典的“正方形非长方形问题”;另外我们知道鸵鸟是不会飞的,但是奔跑的速度很快,以鸵鸟为例。

  • 顶层的抽象动物类
public class Animal {
  /**
   * 米每秒
   */
  private long moveSpeed;

  public long getMoveTime(long distance) {
    return distance / moveSpeed;
  }

  public void setMoveSpeed(long moveSpeed) {
    this.moveSpeed = moveSpeed;
  }
}
  • 较为一般的鸟类
public class Bird extends Animal {

    private long flySpeed;

    public void setFlySpeed(long flySpeed) {
        this.flySpeed = flySpeed;
    }

    public long getFlyTime(long distance) {
        return distance / flySpeed;
    }
}

在定义的过程,无非就是根据一些鸟类的特性,比如有羽毛,会飞,有喙等等;但是往往会存在特例。鸵鸟除了没有的能力其他都是包含的,如果继承Bird类,当求导飞行速度时势必会出现错误,因为鸵鸟的飞行速度为0。

  • 具体到某一种鸟类-麻雀
public class Sparrow extends Bird {
    
    @Override
    public void setFlySpeed(long flySpeed) {
        super.setFlySpeed(flySpeed);
    }
}
  • 鸵鸟类(错误的继承)
public class Ostrich extends Bird {

    @Override
    public void setFlySpeed(long flySpeed) {
        //鸵鸟的飞行速度为零,重写了
        flySpeed = 0;
        super.setFlySpeed(flySpeed);
    }
}

当测试时,肯定是会出现系统异常的情况,这里违背了里氏替换的原则-不能覆盖基类的非抽象方法;从而导致了错误的结果,此时应该考虑取消继承关系,改为更加通用的基类,也即继承Animal,动物都有移动的速度。

  • 鸵鸟类继承Animal
public class Ostrich extends Animal {

    public Ostrich() {}

    @Override
    public void setMoveSpeed(long moveSpeed) {
        super.setMoveSpeed(moveSpeed);
    }

  public static void main(String[] args) {
    //测试
      Animal ostrich = new Ostrich();
      ostrich.setMoveSpeed(90);
  }
}
  • 实际开发的过程中应避免对滥用继承,实现子类时遵循里氏替换的原则能够帮助我们对子类更好地约束,建立起更健壮、易维护的系统。当然不遵循程序也能跑,随着项目的复杂度增加,出现问题的概率也大大增加。
三、依赖倒置原则

高层结构的模块不应该依赖低层结构的模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。

1.一般含义
  • 通俗的解释,依赖倒置的核心思想-面向接口编程。面向接口编程的好处不言而喻,相对于实现细节的多变性,抽象的概念则稳定的多,很多同学包括自己在实际开发中有时候也会陷入到实现的细节中,试想以具体的实现类来构建系统自然是不够稳定的,同样不利于扩展。对于这种,首先考虑的是制定抽象的接口、抽象类层,以接口来约束和规范实现,而不关心具体的实现细节。
2.作用
  • 既然都面向了接口,类与类之间的耦合度降低了(依赖倒置原则降低了类之间的耦合度)。
  • 耦合度低,提高了系统的稳定性(稳定性)。
  • 抽象的规范与约束作用,提高了代码的可维护性,可读性,当然既然存在继承,那么在设计与实现的过程中应遵循里氏替换原则(可维护性、可读性)。
3.如何设计
  • 面向接口-尽量使用使用接口或者抽象类,或者两者都包含来代替类传递。
  • 对于变量的申明类型尽量使用接口或者抽象类,而不是具体的实现类。
  • 继承遵循里氏替换原则
4.实际问题

以大学生学习课程为例

  • 定义课程的接口
/**
 * Created by Sai
 * on: 05/01/2022 23:54.
 */
public interface ICourse {
    void selected();
}
  • 具体课程类-物理课
/**
 * Created by Sai
 * on: 05/01/2022 23:58.
 */
public class PhysicsCourse implements ICourse {

    @Override
    public void selected() {
        System.out.println("物理课被选修了");
    }
}
  • 具体课程类-英语课
/**
 * Created by Sai
 * on: 06/01/2022 00:00.
 */
public class EnglishCourse implements ICourse {

    @Override
    public void selected() {
        System.out.println("英语课被选修了");
    }
}
  • 学生类
/**
 * Created by Sai
 * on: 06/01/2022 00:01.
 */
public class Student {
    //依赖注入
    private ICourse course;
  
    public Student() {}

    public ICourse getCourse() {
        return course;
    }

    public void setCourse(ICourse course) {
        this.course = course;
    }

    public void study() {
        if (null != course) {
            course.selected();
        }
    }

  public static void main(String[] args) {
    Student stu = new Student();
    stu.setCourse(new EnglishCourse());
    stu.study();
    stu.setCourse(new PhysicsCourse());
    stu.study();
  }
}

//英语课被选修了
//物理课被选修了
//Process finished with exit code 0
  • 前面提到依赖倒置的核心-面向接口编程,理解了面向接口编程的含义与运用,依赖倒置原则自然而然就掌握了,当然这离不开实践过程中的积累与思考。
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容