六大设计原则

学习一个语言的语法,可能只需要一两天,然后就具备了搬砖的能力。但是如何做出可扩展、易维护、好管理的工程代码,还需要再补充一些知识和经验。本次先来看看设计原则。

“好的功能设计,在迭代开发时,不需要改动大量的代码,就像火车加一节车厢,插线板支持拔插各种插头,签字笔更换笔芯”

1.单一职责原则

单一指责原则:规定一个类应该只有一个发生变化的原因(职责)

反例

public class AnimalServiceImpl {
    public void say(String animalType){
        if("小狗".equals(animalType)){
            System.out.println("汪汪汪");
        } else if("小猫".equals(animalType)){
            System.out.println("喵喵喵");
        }
    }
}

一个类承担包含多种不同的行为,但是在后续新增或变动需求的时候,继续在类上扩展添加逻辑,就会变得非常臃肿。

修改:

public interface AnimalService {
    void say(String animalType);
}

public class DogServiceimpl implements AnimalService{

    @Override
    public void say(String animalType) {
        System.out.println("汪汪汪");
    }
}

public class CatServiceimpl implements AnimalService{

    @Override
    public void say(String animalType) {
        System.out.println("喵喵喵");
    }
}

定义动物的上层接口,将需要做的事抽象出来。猫和狗分别实现这个接口,互相不干扰,当新增一种动物的时候,也会比较方便,可读性也大大提升。

2.开闭原则

开闭原则:在面向对象领域中,规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的。通过这个原则,要求用抽象定义结构,用具体实现细节(即,面向抽象编程)。

主要的实现思路就是:继承父类,扩展需要的方法,同时保留原有的方法,新增自己需要的方法。这样一来,既扩展的方法,又保留的原有方法。

例如下面计算面积的类,提供了多种形状面积的计算方法

import cn.bugstack.design.ICalculationArea;
/**
 * Create by 小傅哥(fustack) @2020
 */
public class CalculationArea implements ICalculationArea {

    private final static double π = 3.14D;

    public double rectangle(double x, double y) {
        return x * y;
    }

    public double triangle(double x, double y, double z) {
        double p = (x + y + z) / 2;
        return Math.sqrt(p * (p - x) * (p - y) * (p - z));
    }

    public double circular(double r) {
        return π * r * r;
    }

}

在这个类已经在工程中普遍使用的情况下,如果需要修改π的精度,在不考虑开闭原则的情况下,可以直接修改π的值。

开闭原则的目的是不能因为个例需求的变化,而改变预定的实现类,除非预定的实现类有错误。

所以此时可以,新建一个类继承CalculationArea,扩展需要的方法,保留原有方法。

/**
 * Create by 小傅哥(fustack) @2020
 */
public class CalculationAreaExt extends CalculationArea {

    private final static double π = 3.141592653D;

    @Override
    public double circular(double r) {
        return π * r * r;
    }

}

3.里氏替换原则

**里氏替换原则:**如果B是A的子类,那么所有B类型的对象都可以在不破坏程序的情况下被A类型的对象替换。 即,子类可以扩展父类的功能,但不能改变父类原有的功能==子类尽量不要重写父类的方法。

含义:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  2. 子类可以增加自己特有的方法;
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松;
  4. 当子类的方法实现父类的方法(重写、重载或实现抽象方法)时,方法的后置条件(即方法的输出或返回值)要比父类的方法更严格或者与父类的方法相等。

作用:

  1. 里氏替换原则是实现开闭原则的重要方式之一;
  2. 解决了继承中重写父类造成的可复用性变差的问题;
  3. 是动作正确的保证,即类的扩展不会给已有的系统引入新的错误,降低出错的可能性;
  4. 加强程序的健壮性,在变更时可以做到很好的兼容性。

反例:

如果不按照原则,随意重写父类的方法,最直接的影响就是会导致子类不再具备父类的功能。无法达到继承扩展且复用的目的。

例如上面计算面积的例子,如果子类在调用circular()方法的时候,始终都是高精度下的计算,无法再回到父类的低精度。

public class CalculationAreaExt extends CalculationArea {

    private final static double π = 3.141592653D;

    /**
     * 扩展高精度之后的面积计算
     */
    public double circularExt(double r) {
        return π * r * r;
    }
}

这样一来,子类既保留了父类低精度的计算,也扩展了高精度的计算。

4.迪米特法则原则

迪米特法则原则(最少知道原则):指一个对象类对于其他对象类来说,知道的越少越好。两个类之间,不要有过多的耦合关系,保持最少的关联性。

这个比较容易理解,例如存在学生、班主任老师、校长三级的对象关系,如果在校长类中,直接提供对学生信息的操作,就违背了迪米特法则,代码会变得臃肿。

可以一级一级的对象操作,教师对学生信息进行操作,校长通过调用教师提供的方法,获得学生的统计信息。

5.接口隔离原则

**接口隔离原则:**一个类对另一个类的依赖应该建立在最小的接口上。其目的是为了实现高内聚、低耦合。

在实际中,要求将臃肿庞大的接口,拆分成多个小接口,并有各个服务类实现。当一个大接口中的很多方法不需要被每一个类都要实现时,这样的接口很难维护,也不易扩展,每一次修改验证都有潜在的风险。

拆分的衡量依据:

  • 接口应该只服务于一个子模块或业务逻辑,并不是无限小的;
  • 为依赖接口的类定制服务,只提供调用者需要的方法,屏蔽不需要的方法;
  • 了解环境,拒绝盲从;根据业务逻辑,定制接口拆分的标准;
  • 提高内聚,减少对外交互:让接口用最少的方法完成最多的事情。

反例接口:

每个动物实现类要实现多个与自己无关的方法,无法控制外部不能调用,需要单独文档说明。

public interface AnimalSkill {
    // 游泳
    void swim();
    // 飞翔
    void fly();
    // 打洞
    void hole();
}

class EagleSkill implements AnimalSkill{

    @Override
    public void swim() {

    }

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

    @Override
    public void hole() {

    }
}

拆解后,只需要实现对应的接口:

public interface ISkillFly {
    // 飞翔
    void fly();
}
class EagleSkill implements ISkillFly{

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

6.依赖倒置原则

依赖倒置原则:代码的高层模块不应该依赖于底层模块,二者都应该依赖于抽象。即,抽象不应该依赖于细节,细节应该依赖于抽象。

是实现开闭原则的重要途径之一,降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读。

例如:小黎喂养自己的多种宠物,我们可以抽象出喂养动作,然后面向这个抽象做不同的实现。

反例:

class FeedControl {
    // 喂狗狗
    void feedDog(){
        System.out.println("喂狗狗");
    }

    // 喂猫猫
    void feedCat(){
        System.out.println("喂肉球");
    }
}

在一个类中,使用多个方法实现。

符合依赖倒置原则:

interface IFeedControl {
    // 喂的抽像
    void feed();
}

class DogFeed implements IFeedControl{
    @Override
    public void feed() {
        System.out.println("喂狗狗");
    }
}

class CatFeed implements IFeedControl{
    @Override
    public void feed() {
        System.out.println("喂肉球");
    }
}

这样一来任何一种抽象都可以有自己的实现类,既可以不断完善,也可以新增。当小黎之后,养其他宠物时,也方便扩展。

小结

名字简述
单一职责原则规定一个类应该只有一个发生变化的原因(职责)
开闭原则规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的
里氏替换原则子类可以扩展父类的功能,但不能改变父类原有的功能
迪米特法则原则指一个对象类对于其他对象类来说,知道的越少越好
接口隔离原则一个类对另一个类的依赖应该建立在最小的接口上
依赖倒置原则代码的高层模块不应该依赖于底层模块,二者都应该依赖于抽象

通过对设计原则的了解,也就明晰了设计模式的一些主要思想,设计模式是对这些原则的各种实现。

文献:《重学java设计模式》

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值