设计模式之设计原则

设计模式之设计原则

本文部分节选和引用自
如何学好设计,做好架构? 核心思想才是关键
感谢作者提供的高质量文章

1.单一设计原则

单一设计原则很好理解,指一个函数或者一个类再或者一个模块,职责越单一复用性就越强,同时能够降低耦合性。

案例:获取本地用户信息,提交到网络

fun post() {
    //创建数据库访问对象DAO
    val userDao = ...
    
    //从本地获取数据
    val name = userDao.getName()
    val age = userDao.getAge()
    
    //发送请求并且携带数据
    val http = ...
    http.post(name, age,...)
}

案例中将,创建获取提交三个步骤写到同一个函数中,很显然违背了单一设计原则,当修改这三步中的逻辑时,都会影响到其他的步骤,当业务逐渐复杂后出现问题的概率就会指数级上升,因此可以通过单一设计原则做一次重构,代码如下:

fun getUserDao():UserDao {
    ...
    return dao
}

fun getUserInfo():UserInfo {
    val dao = getUserDao()
    val userInfo = UserInfo()
    userInfo.name = dao.getName()
    userInfo.age = dao.getName()
    ...
    return userInfo
}

fun post() {
    val userInfo = getUserInfo()
    getHttp().post(usetInfo.name,userInfo.age,...)
}

将逻辑独立的三步拆成三个函数,从根本上杜绝改动带来的额问题,在设计代码的时候不要先直接开始写,可以先考虑一下模块、类、函数的设计是否足够单一

2.开闭原则

一句话概括开闭原则对扩展开放,修改关闭。它充分地诠释了抽象多态的特性,并且也是多数行为型设计模式的基础,遍布于各大优秀框架之中,是最重要的一条设计原则,光是这一条设计原则就能吧你的设计能力提升百分之四十以上

案例:通过SQLite做CRUD操作

class SQLiteDao{

    public void insert(){
        ...
    }
    
    public void delete(){
        ...
    }
}

SQLiteDao dao = new SQLiteDao();
dao.insert();
...

以上就是简单粗暴的写法,但是存在一个致命问题,如果摸一天想替换SQLite业务层基本要把调用dao对象的地方都动一遍,改动就会存在出错的可能,并且需要做大量的重复操作

下面就用利用抽象、多态基于开闭原则重构,代码如下

interface IDao{
    void insert();
    void delete();
}

class SQLite imlements IDao {
    
    @Override
    public void insert(){
        ...
    }
    
    @Override
    public void delete(){
        ...
    }
}

class RoomDao implements IDao{
    @Override
    public void insert() {
        //通过Room做insert
    }
    @Override
    public void delete() {
        //通过Room做delete
    }
}

//扩展点
IDao dao = new SQLiteDao();
dao.insert();
  • 定义功能接口IDao
  • 定义类SQLiteDaoRoomDao并实现IDao的功能
  • 业务基于接口IDao进行编程

重构后,需要将SQlite替换至Room,只要将注释扩展点处的SQLiteDao替换成RoomDao即可,其他地方完全不同改动。这就是所谓的扩展开放,修改关闭

在业务不断迭代的情况下,唯一不变的就是改变,这种背景下我们能做的只有在代码中基于开闭原则多留扩展点以不变应万变。

3.迪米特原则

基本概念:不该有直接依赖关系的模块不要有依赖。有依赖关系的模块之间,尽量只依赖必要的接口。

迪米特原则很好理解并且很实用,违背迪米特原则会产生什么问题?

案例:

class Wallet{
    /**
     * 余额
     */
    int balance;

    /**
     * 存钱
     */
    void saveMoney(int money){
        balance += money;
    }

    /**
     * 花钱
     */
    void spendMoney(int money){
        balance -= money;
    }
}

Wallet的设计违背了迪米特法则,毕竟外部只需要savespend功能,将balance暴漏使用者就有权限直接修改其值,可能会对整个Wallet功能造成影响。此时应基于迪米特法则对Wallet进行改造,将balance通过封装特性增加private修饰符

迪米特法则和单一设计原则很像,前者符合松耦合后者符合高内聚

4.接口隔离原则

基本概念:接口的调用者不应该依赖它不需要的接口。

interface Callback{
    /**
     * 点击事件回调方法
     */
    void clickCallback();
    /**
     * 滚动事件回调方法
     */
    void scrollCallback();
}

接口Callback包含点击、滚动两个回调方法,面临的问题有两个:

  • 某些特定场景使用者只需要依赖点击回调,那滚动回调便成了多余,把外部不需要的功能暴露出来就存在误操作的可能。
  • 点击和滚动本来就是两种特性,强行揉到一块只能让接口更臃肿,进而降低其复用性

根据接口隔离原则改造后如下:

interface ClickCallback{
    /**
     * 点击事件回调方法
     */
    void clickCallback();
}

interface ScrollCallback{
    /**
     * 滚动事件回调方法
     */
    void scrollCallback();
}

基于单一设计原则把点击和滚动拆分成两个接口,将模块间隔离的更彻底。并且由于粒度更细,所以复用性也更高

接口隔离原则与迪米特法则目的很相似,都可以降低模块间依赖关系。但接口隔离更侧重于设计单一接口,提升复用性并间接降低模块间依赖关系,而迪米特法则是直接降低模块间依赖关

5.里式替换原则

基本概念:

设计子类的时候,要遵守父类的行为约定。父类定义了函数的行为约定,子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定

里氏替换非常简单并且很容易遵守,在使用继承时,允许复写父类方法,但不要改变其功能。比如自定义View,子类的onMeasure中一定要调用setMeasureaDimission()方法(或者直接使用super),否则会影响父类方法功能(会抛异常),也既违背了里氏替换原则。

6.依赖倒置原则

控制反转: 提及依赖倒置便不得不提控制反转,一句话概括:将复杂的程序操作控制权由程序员交给成熟的框架处理,程序员->成熟的框架为反转,框架应暴露出扩展点由程序员实现

什么是依赖倒置?

高层模块(使用者)不应依赖低层模块(被使用者),它们共同依赖同一个抽象,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

其实核心点就是基于接口而非实现编程,2数据库案例也符合依赖倒置原则,高层模块(业务层)不依赖于低层模块(SQLiteDao/RoomDao),而是依赖于抽象(IDao),可见依赖倒置也是开闭原则扩展而来。
区别是依赖倒置更侧重于指导框架的设计,框架层应该尽量将更多的细节隐藏在内部,对外只暴露抽象(抽象类/接口),指导框架设计这方面核心就是控制反转

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值