设计模式(一)—— 设计原则

设计模式(一)—— 设计原则

开闭原则

开闭原则是指一个软件实体(类,模块,函数等)应该对扩展开放,对修改关闭。所谓的开闭也是对拓展和修改行为的原则。它强调的是用抽象构建框架,用实现拓展细节,可以提高软件系统的可复用性和维护性。开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定,灵活的系统。开闭原则的核心思想就是面向抽象编程
我们以手机为例, 首先创建一个手机接口 Phone:

package com.chunqiu.ocp;

public interface Phone {
    Integer getId();
    Double getPrice();
}

手机有很多的品牌:小米,苹果,华为……我们来创建一个苹果手机的类:ApplePhone:

package com.chunqiu.ocp;

public class ApplePhone implements Phone{
    private Integer id;
    private Double price;

    public ApplePhone(Integer id, Double price) {
        this.id = id;
        this.price = price;
    }

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

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

现在我们要给苹果手机打折,促进消费,但是如果修改ApplePhone 中的getPrice()方法,会存在一定的风险,因为其他的地方可能也会调用这个方法,违背了我们的初衷,会影响其他地方的调用结果。那我们要怎么样在不修改原有代码的前提下实现打折的功能呢?那么现在我们写一个处理打折逻辑的类:DiscountApplePhone

package com.chunqiu.ocp;

public class DiscountApplePhone extends ApplePhone{
    public DiscountApplePhone(Integer id, Double price) {
        super(id, price);
    }
    // 实现打折
    public Double getPrice() {
        return  super.getPrice() * 0.8;
    } 
    
    public Double getOldPrice() {
        return  super.getPrice();
    }
}

这样就实现了对拓展开放有没有对原来的代码修改。

依赖倒置原则

依赖倒置原则是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。通过 依赖倒置可以减少类之间的耦合性,提高形同的稳定性,提高代码的可读性和可维护性,并且可以降低修改程序所造成的风险。
我们来看一个例子,小明喜欢收集收集,有了新品就会买。我们先来创建一个类Ming

package com.chunqiu.dip;

public class Ming {
    public void buyXiaoMi() {
        System.out.println("小明买了最新的小米手机");
    }

    public void buyApple() {
        System.out.println("小明买了最新的苹果手机");
    }
}

调用方法:

public static void main(String[] args) {
        Ming ming = new Ming();
        ming.buyApple();
        ming.buyXiaoMi();
    }

但是最近又出了新的华为,要实现这个逻辑要从低层到高层(调用层)依次修改代码。在Ming 类中加上buyHuawei()方法,在调用层也要追加调用。这样的话拓展性不仅受到了限制,对于我们的系统也是有很大的风险。其实想一下,买手机的这个动作变化的只是手机的品牌,这一部分我们其实是可以抽取出来的。所以我们这样来进行优化代码:

  1. 创建一个手机的抽象接口:Phone:
package com.chunqiu.dip;

public interface Phone {
    void buy();
}

然后编写XiaomiPhone类:

package com.chunqiu.dip;

public class XiaomiPhone implements Phone{
    @Override
    public void buy() {
        System.out.println("小明买了最新的小米手机!");
    }
}

然后编写ApplePhone类:

package com.chunqiu.dip;

public class ApplePhone implements Phone{
    @Override
    public void buy() {
        System.out.println("小明买了最新的苹果手机!");
    }
}

修改Ming的购买方法:

package com.chunqiu.dip;

public class Ming {
    public void buy(Phone phone) {
        phone.buy();
    } 
}

调用方法:

 public static void main(String[] args) {
        Ming ming = new Ming();
        ming.buy(new XiaomiPhone());
        ming.buy(new ApplePhone());
    }

这时候我们再来看,无论之后有什么品牌的手机再出新品,我们只需要新建一个类,通过传参的方式告诉小明就可以了,而不需要修改底层的代码。

单一职责原则

单一职责是指不要存在多于一个导致类变更的原因,也就是说一个类只负责一个职责。否则一旦需求变更,修改其中一个职责的代码可能会导致另一个职责的功能发生故障。如果一个类中存在两个职责,那么应该每一个职责应该对应一个类,把这个类进行拆分,解耦。后期需求变更维护互不影响。这样的话可以降低类的复杂度,提高代码的可读性,提高系统的可维护性,降低变更引起的风险。
我们来看一个例子:

package com.chunqiu.srp;

public class Animal {
    public void move(String type) {
        if ("bird".equals(type)) {
            System.out.println("fly");
        } else {
            System.out.println("run");
        }
    }
}

Animal 承担了两种处理逻辑。假如现在有一条鱼,就必须要在修改代码。我们应该使得我们的代码是松耦合的。我们来建立两个类:Bird和Dog让他们可以自己做自己的事情,互相隔离:

package com.chunqiu.srp;

public class Bird {
    public void move() {
        System.out.println("fly");
    }
}

package com.chunqiu.srp;

public class Dog {
    public void move() {
        System.out.println("run");
    }
}

接口隔离原则

接口隔离原则是指用多个专门的接口,而不是使用单一的总接口,客户端不应该实现他不需要的接口。我们应该注意以下几点:
1.一个类对另一个类的依赖应该建立在最小接口之上
2.建立单一接口,不要建立庞大的臃肿的接口
3.尽量的细化接口,接口的方法尽量少,当然要结合自己的实际情况

接口隔离原则符合我们常说的高内聚,低耦合的设计思想,可以使类具有良好的可读性,可维护性和可拓展性。
还是以动物为例:

package com.chunqiu.srp;

public interface Animal {
    void fly();
    void eat();
    void run();
}

其中有两个实现类Bird和Dog,但是Dog却不得不实现Animal 的所有方法,那么fly也要实现,不太现实的对吧?这时候我们就要对不同的动物行为来设计不同的接口。也就是说我们需要一个接口负责对的事,不要添加自己不负责的事情。所以我们可以拆分为三个接口:Eat,Fly, Run.

迪米特原则

迪米特原则又称最少知道原则,是指一个对象应该对其他的对象保持最少的了解。尽量降低类和类之间的耦合度。迪米特原则强调只和朋友交流,不喝陌生人说话。出现在成员变量,方法的输入输出参数中的类都可以成为成员朋友类,而出现在方法体内部的类不属于朋友类。
假如领导要我们统计加班情况,我们统计好之后再把结果告诉领导。我们来实现一下:
Leader:

package com.chunqiu.srp;

import java.util.List;

public class Leader {
    public void countOT(List<OT> ot) {
        System.out.println("加班人数:" + ot.size());
    }
}

Boss:

package com.chunqiu.srp;

import java.util.ArrayList;
import java.util.List;

public class Boss {
    public void count(Leader leader) {
        List<OT> ot = new ArrayList<>();
        // 模拟统计加班
       for (int i = 1; i <10; i++) {
           ot.add(new OT());
       }
       leader.countOT(ot);
    }
}

测试:

 public static void main(String[] args) {
        Boss boss = new Boss();
        Leader leader = new Leader();
        boss.count(leader);
    }

到这里我们的代码完成了,功能也已经实现,看上去没什么问题。但是根据上边我们说的,领导只想要leader统计好加班的情况,要一个汇总的情况,不需要和加班的人直接去交流。而leader统计是还需要使用OT对象的。所以领导和OT并不是朋友。所以我们应该优化:
leader:

package com.chunqiu.srp;

import java.util.ArrayList;
import java.util.List;

public class Leader {
    public void countOT() {
        List<Integer> ot = new ArrayList<>();
        // 模拟统计加班
        for (int i = 1; i <10; i++) {
            ot.add(i);
        }
        System.out.println("加班人数:" + ot.size());
    }
}

Boss:

package com.chunqiu.srp;

import java.util.ArrayList;
import java.util.List;

public class Boss {
    public void count(Leader leader) {
        leader.countOT();
    }

    public static void main(String[] args) {
        Boss boss = new Boss();
        Leader leader = new Leader();
        boss.count(leader);
    }
}

这样的话Boss和OT就没有关联了。

里氏替换原则

里氏替换原则是说如果对每一个类型为T1 的对象o1,都有类型为T2 的对象o2,使得T1定义的所有程序P在所有的对象o1都替换成o2,程序P 的行为没有发生变化,那么类型T2是类型T1的子类型。
上边的说法很抽象,我们可以理解为一个软件实体如果适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替代父类对象,而程序的逻辑不变。也可以得出一个结论:子类可以拓展父类的功能,但不能改变父类原有的功能。
1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
2.子类可以增加自己特有的方法
3.当子类的方法重载父类的方法是,方法的前置条件要比父类方法的输入参数更宽松。
4,当子类的方法重载父类的方法时,方法的后置条件要比父类更严格或和父类一样。

使用里氏替换原则有两个优点:
1.约束继承泛滥,是开闭原则的一种体现
2.加强程序的健壮性,在变更的时候也可以做到非常好的兼容性,提高程序的可维护性和拓展性,降低需求变更时的风险。

合成复用原则

合成复用原则是指尽量使用对象组合、聚合而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类之间的耦合度。
继承叫做白箱复用,把所有的实现细节暴露给子类,组合聚合成为黑箱复用,无法获取到类以外的对象的实现细节的。
我们来模拟一个数据库连接的操作:

DBConnection :

package com.chunqiu.srp;

public class DBConnection {
    public String getConnection() {
        return "mysql 连接";
    }
}

ProductDao:

package com.chunqiu.srp;

public class ProductDao {
    private DBConnection connection;

    public void setConnection(DBConnection connection) {
        this.connection = connection;
    }
    public void add() {
        String connection = this.connection.getConnection();
        System.out.println("添加产品");
    }
}

这是一种合成复用的场景。但是DBConnection 目前只有mysql,之后添加其他的数据库,要怎么做?在DBConnection 添加对其他数据库的支持,但是违背了开闭原则,所以我们需要对DBConnection 进一步抽象:

public abstract class DBConnection {
    public abstract String getConnection();
}

然后将其他数据库的逻辑抽出来进行具体化:

package com.chunqiu.srp;

public class MysqlDBConnection  extends  DBConnection{
    @Override
    public String getConnection() {
        return "mysql连接";
    }
}

这样的话也可以支持其他的数据库。

总结

设计模式的原则带给我们更重要的是一种思想,对于我们在实际的开发中有深远的意义,有利于规范我们的开发习惯,提升我们的能力。我们要让我们的代码专于自己的职责,为了以后系统的可拓展性和维护性尽量要进行解耦。设计原则是基于抽象进行架构,要面向接口编程,先顶层在细节的设计代码结构。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹升茉莉清

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值