设计模式之美-1-面向对象

本文探讨了面向对象编程的核心概念,包括封装、抽象、继承和多态,以及它们在设计和实现中的应用。对比了面向过程编程,强调了OOP在复杂系统中的优势。讨论了接口与抽象类的区别,提倡基于接口编程以提高代码灵活性和扩展性。
摘要由CSDN通过智能技术生成

设计原则与思想: 面向对象

1.面向对象编程

1.1 面向对象编程v.s.面向对象编程语言

  • 面向对象编程(object oriented programming, OOP): 一种编程范式或者编程风格, 以类或对象作为组织代码的基本单元, 并将封装, 抽象, 继承, 多态四个特性, 作为代码设计和实现的基石.
  • 面向对象编程语言(object oriented programming language, OOPL): 支持类或对象的语法机制, 并有现成的语法机制, 能方便地实现面向对象编程四大特性(封装, 继承, 抽象, 多态)的编程语言.

1.2 面向对象分析, 设计, 编程

  1. 面向对象分析(object oriented analysis, OOA): 围绕对象或类的需求分析.
  2. 面向对象设计(object oriented design, OOD): 围绕对象或类的系统设计.
  3. 面向对象编程(object oriented programming, OOP): 将需求分析和系统设计转化为代码的过程.

2.面向对象编程的四大特性

理解OOP和OOPL的关键就是理解其四大特性: 封装, 抽象, 继承, 多态

2.1 封装 encapsulation

封装也叫做信息隐藏或者数据访问保护.类通过暴露有限的访问接口, 授权外部仅能使用类提供的方式(函数)来访问内部信息或者数据.需要编程语言提供权限访问控制语法来支持.

public class Wallet {
    private String id;
    private long createTime;
    private BigDecimal balance;
    private long balanceLastModifiedTime;
    // ...省略其他属性...

    public Wallet() {
        this.id = IdGenerator.getInstance().generate();
        this.createTime = System.currentTimeMillis();
        this.balance = BigDecimal.ZERO;
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }

    // 注意: 下面对get方法做了代码折叠, 是为了减少代码所占文章的篇幅
    public String getId() { return this.id; }
    public long getCreateTime() { return this.createTime; }
    public BigDecimal getBalance() { return this.balance; }
    public long getBalanceLastModifiedTime() { return this.balanceLastModifiedTime;  }

    public void increaseBalance(BigDecimal increasedAmount) {
        if (increasedAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new InvalidAmountException("...");
        }
        this.balance.add(increasedAmount);
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }

    public void decreaseBalance(BigDecimal decreasedAmount) {
        if (decreasedAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new InvalidAmountException("...");
        }
        if (decreasedAmount.compareTo(this.balance) > 0) {
            throw new InsufficientAmountException("...");
        }
        this.balance.subtract(decreasedAmount);
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }
}

这段代码是金融系统中一个简化版的虚拟钱包的代码实现.在金融系统中, 我们会给每个用户创建一个虚拟钱包, 用来记录用户在我们的系统中的虚拟货币量.Wallet类主要有四个属性(成员变量), 其中id表示钱包的唯一编号, createTime表示钱包创建的时间, balance表示钱包中的余额, balanceLastModifiedTime表示上次钱包余额变更的时间.

我们参照封装特性, 对钱包的这四个属性的访问方式进行了限制.调用者只允许通过类提供的六个方法来访问或者修改钱包里的数据.

从业务的角度来说, id, createTime在创建钱包时确定, 之后不能再改动, 所以Wallet类中没有暴露id, createTime这两个属性的任何修改方法(如set方法).而且, 这两个属性的初始化设置, 对于Wallet类的调用者来说, 应该是透明的, 所以在Wallet类的构造函数内部将其初始化设置好, 而不是通过构造函数的参数来外部赋值.

对于钱包余额balance这个属性, 从业务的角度来说, 只能增或者减, 不会被重新设置.所以Wallet类中, 只暴露了increateBalance()和decreateBalance()方法, 并没有暴露set方法.对于balanceLastModifiedTime这个属性, 它完全根balance这个属性的修改操作绑定在一起.只有在balance修改的时候, 这个属性才会被修改.所以balanceLastModifiedTime这个属性的修改操作完全封装在了increateBalance()和decreateBalance()两个方法中, 不对外暴露任何修改这个属性的方法和业务细节.这样也可以保证balance和balanceLastModifiedTime两个数据的一致性.

对于封装这个特性, 需要编程语言本身提供访问控制权限的语法机制来支持.Java语言通过private, protected, public关键字实现类内属性和方法的权限控制, private表示私有属性和方法, 只能在类的内部访问和修改, protected表示受保护的属性和方法, 只能在自身, 子类和同一个包中的类访问, public表示公有属性和方法, 可以被所有其他类访问.python语言通过双下划线和单下划线实现类内属性和方法的权限控制, __xxx__表示特殊变量和方法, __xxx表示私有属性和方法, 只有内部可以访问, _xxx表示受保护的属性和方法, xxx表示公有的属性和方法.

如果我们对类中属性的访问不做限制, 那任何代码都可以访问, 修改类中的属性, 会导致属性可以随意被以各种奇葩的方式修改, 而且修改逻辑可能散落在代码中的各个角落, 势必影响代码的可读性, 可维护性.除此之外, 类仅仅通过有限的方法暴露必要的操作, 也能提高类的易用性.

2.2 抽象 abstraction

封装讲的是如何隐藏信息, 保护数据, 而抽象讲的是隐藏方法的具体实现, 让调用者只关心方法提供的功能, 不需要知道实现实现细节.

在面向对象编程中, 常借助编程语言提供的接口(如Java语言的interface关键字语法)或者抽象类(如Java语言的abstract关键字语法)这两种语法机制来实现抽象这一特性, 也可以不需要特殊的语法机制实现.

public interface IPictureStorage {
    void savePicture(Picture picture);
    Image getPicture(String pictureId);
    void deletePicture(String pictureId);
    void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
    }

public class PictureStorage implements IPictureStorage {
    // ...省略其他属性...
    @Override
    public void savePicture(Picture picture) { ... }
    @Override
    public Image getPicture(String pictureId) { ... }
    @Override
    public void deletePicture(String pictureId) { ... }
    @Override
    public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}

在这段代码中, 利用java语言中的interface接口语法来实现抽象特性.调用者在使用图片存储功能的时候, 只需要了解IPictureStorage这个接口类暴露了哪些方法就可以了, 不需要去查看PictureStorage类里的具体实现逻辑.

事实上, 抽象这个特性非常容易实现, 并不需要非得依靠接口类或者抽象类这些特殊语法机制来支持.换句话说, 并不是说一定要为实现类(PictureStorage)抽象出接口类(IPictureStorage)才叫做抽象.即使不编写IPictureStorage接口类, 单纯的PictureStorage类本身就满足抽象特性.

之所以这么说, 是因为类的方法是通过编程语言中的“函数”这一语法机制来实现的.通过函数包裹具体的实现逻辑, 这本身就是一种抽象.调用者在使用函数的时候, 并不需要去研究函数内部的实现逻辑, 只需要通过函数的命名, 注释或者文档, 了解其提供了什么功能, 就可以直接使用了.

封装和抽象都是人类处理复杂性的有效手段.在面对复杂系统的时候, 人脑能承受的信息复杂程度是有限的, 所以我们必须忽略掉一些非关键性的实现细节.而抽象作为一种只关注功能点而不关注实现的设计思路, 正好帮我们的大脑过滤掉许多非必要的信息.

除此之外, 抽象作为一个非常宽泛的设计思想, 在代码设计中, 起到非常重要的指导作用.很多设计原则都体现了抽象这种设计思想, 比如基于接口而非实现编程, 开闭原则(对扩展开放, 对修改关闭), 代码解耦(降低代码的耦合性)等.

换一个角度来思考, 我们在定义或者命名类的方法的时候, 也要有抽象思维, 不要在方法定义中, 暴露太多的实现细节, 以保证在某个时间点需要改变方法的实现逻辑的时候, 不用去修改其定义.

2.3 继承 inheritance

继承用来表示类之间的is-a关系, 比如猫是一种哺乳动物.从继承关系来讲, 继承可以分为单继承(一个子类只继承一个父类)和多继承(一个子类可以继承多个父类), 比如猫既是哺乳动物, 又是爬行动物.

为了实现继承这个特性, 编程语言需要提供特殊的语法机制来支持, 比如Java使用extends关键字来实现继承, C++使用冒号(class B: public A)表示继承关系, Python使用()表示继承关系.不过Java, PHP, C#等只支持单继承, 而C++, Python等还支持多重继承.

继承最大的一个好处就是代码复用.假设两个类有一些相同的属性和方法, 我们就可以将这些相同的部分, 抽取到父类中, 让两个子类继承父类.这样, 两个子类就可以重用父类中的代码.不过我们也可以使用其他方式来解决代码复用的问题, 如使用组合关系而不是继承关系.

继承的概念很好理解, 也很容易使用.不过, 过度使用继承, 继承层次过深过复杂, 就会导致代码可读性, 可维护性变差.为了了解一个类的功能, 我们不仅需要查看这个类的代码, 还需要按照继承关系一层一层往上查看父类的代码.还有, 子类和父类高度耦合, 修改父类的代码, 会直接影响到子类.所以继承这个特性也是一个非常有争议的特性.

2.4 多态 polymorphism

多态是指, 子类可以替换父类, 在代码运行过程中调用子类的方法实现.

public class DynamicArray {
    private static final int DEFAULT_CAPACITY = 10;
    protected int size = 0;
    protected int capacity = DEFAULT_CAPACITY;
    protected Integer[] elements = new Integer[DEFAULT_CAPACITY];

    public int size() { return this.size; }
    public Integer get(int index) { return elements[index];}
    //...省略n多方法...

    public void add(Integer e) {
        ensureCapacity();
        elements[size++] = e;
    }

    protected void ensureCapacity() {
        //...如果数组满了就扩容...代码省略...
    }
}

public class SortedDynamicArray extends DynamicArray {
    @Override
    public void add(Integer e) {
        ensureCapacity();
        int i;
        for (i = size-1; i>=0; --i) { //保证数组中的数据有序
            if (elements[i] > e) {
                elements[i+1] = elements[i];
            } else {
                break;
            }
        }
        elements[i+1] = e;
        ++size;
    }
}

public class Example {
    public static void test(DynamicArray dynamicArray) {
        dynamicArray.add(5);
        dynamicArray.add(1);
        dynamicArray.add(3);
        for (int i = 0; i < dynamicArray.size(); ++i) {
            System.out.println(dynamicArray.get(i));
        }
    }

    public static void main(String args[]) {
        DynamicArray dynamicArray = new SortedDynamicArray();
        test(dynamicArray); // 打印结果: 1, 3, 5
    }
}

多态这种特性也需要编程语言提供特殊的语法机制来实现.上面的例子中用到了如下三个语法机制.

  1. 编程语言要支持父类对象可以引用子类对象, 即可以将SortedDynamicArray传递给DynamicArray.
  2. 编程语言要支持继承, 即SortedDynamicArray继承了DynaimcArray, 才能将SortedDynamicArray传递给DynamicArray.
  3. 编程语言要支持子类可以重写(override)父类中的方法, 即SortedDynaimcArray重写了DynamicArray中的add()方法.

对于多态特性的实现方式, 除了上述展示的继承加方法重写之外, 还有其他两种比较常见的实现方式, 一个是利用接口类语法, 另一个是利用duck-typing语法.不过并不是每一种编程语言都支持接口类或者duck-typing这两种语法机制, 比如C++就不支持接口类语法, 而duck-typing只有如Python, JavaScript等一些动态语言才支持.

    public interface Iterator {
        String hasNext();
        String next();
        String remove();
    }

    public class Array implements Iterator {
        private String[] data;

        public String hasNext() { ... }
        public String next() { ... }
        public String remove() { ... }
        //...省略其他方法...
    }

    public class LinkedList implements Iterator {
        private LinkedListNode head;

        public String hasNext() { ... }
        public String next() { ... }
        public String remove() { ... }
        //...省略其他方法... 
    }

    public class Demo {
        private static void print(Iterator iterator) {
            while (iterator.hasNext()) {
            System.out.println(iterator.next());
            }
        }

        public static void main(String[] args) {
            Iterator arrayIterator = new Array();
            print(arrayIterator);

            Iterator linkedListIterator = new LinkedList();
            print(linkedListIterator);
        }
    }

上面是一段利用接口类实现多态特性的代码.其中Iterator是一个接口类, 定义了一个可以遍历集合数据的迭代器.Array和LinkedList都实现了接口类Iterator.我们通过传递不同类型的实现类(Array, LinkedList)到print(Iterator iterator)函数中, 支持动态的调用不同的next(), hasNext()实现方法.

    class Logger:
        def record(self):
            print(“I write a log into file.”)

    class DB:
        def record(self):
            print(“I insert data into db. ”)

    def test(recorder):
        recorder.record()

    def demo():
        logger = Logger()
        db = DB()
        test(logger)
        test(db)

上面是一段使用duck-typing实现多台特性的Python代码, 可以看到这种方式非常灵活.Logger和DB两个类没有任何关系, 既不是继承关系, 也不是接口和实现的关系, 但是只要它们都定义了record()方法, 就可以被传递到test()方法中, 在实际运行的时候, 执行对应的record()方法.

也就是说, 只要两个类具有相同的方法, 就可以实现多态, 并不要求两个类之间有任何关系, 这就是所谓的duck-typing, 是一些动态语言所特有的语法机制.而像Java这样的静态语言, 通过继承实现多态特性, 必须要求两个类之间有继承关系, 通过接口实现多态特性, 类必须要实现对应的接口.

多态特性能提高代码的可扩展性和复用性.除此之外, 多态也是很多设计模式, 设计原则, 编程技巧的代码实现基础, 比如策略模式, 基于接口而非实现编程, 依赖倒置原则, 里式替换原则, 利用多态去掉冗长的if-else语句等等.

  • Python对于四大特性的实现:
  1. 抽象: 抽象是编程语言的共有特点, 甚至是计算机科学的特点, 从变量, 函数, 类, 模块, 包等概念都是不同层次的抽象.抽象和把大象装进冰箱分三步是一个道理, 它给出了思路, 指明了方向, 省略了细节.我们用层层抽象来应对计算机系统的复杂性.Python主要的抽象工具是函数和类, 模块和包也算吧, 毕竟也是隐藏了细节.

  2. 封装: Python不支持严格意义上的封装, 没有private, protected等访问修饰符, 这样做是为了保证动态语言最大的灵活性, 同时Python里很多理念都是约定大于定义的, 私有的属性需要大家守约, 不要去随意访问, 这也是Python被吐槽的地方吧, 大型项目约束力不够.

  3. 继承: Python支持多重继承, 主要是因为它没有类似于Java的“接口类”的语法吧, 用多重继承可以定义一些纯功能性的类, 减少类的层级.

  4. 多态: Python的多态就是鸭子类型了, 鸭子类型的背后是所谓“协议”, 协议是非正式的接口, 是一种特性, 表现为一个或多个相关的方法, 比如迭代器协议, 序列协议.实现了迭代器协议就和Java中实现了Iterator接口一样.

3.面向对象v.s.面向过程

除了面向对象之外, 被大家熟知的编程范式还有面向过程编程和函数式编程.面向过程编程这种编程范式随着面向对象的出现已经慢慢退出舞台, 而函数式编程目前还没有被广泛接受.

3.1面向过程编程与面向过程编程语言

  • 面向过程编程也是一种编程范式或编程风格.它以过程(可以理解为方法, 函数, 操作)作为组织代码的基本单元, 以数据(可以理解为成员变量, 属性)与方法相分离作为最主要的特点.面向过程风格是一种流程化的编程风格, 通过拼接一组顺序执行的方法来操作数据完成一项功能.

  • 面向过程编程语言首先是一种编程语言.它最大的特点是不支持类和对象这两个语法概念, 不支持丰富的面向对象编程特性(如继承, 多态, 封装), 仅支持面向过程编程.

使用C语言编写的面向过程风格的代码:

struct User {
    char name[64];
  int age;
  char gender[16];
};

struct User parse_to_user(char* text) {
  // 将text(“小王&28&男”)解析成结构体struct User
}

char* format_to_text(struct User user) {
  // 将结构体struct User格式化成文本("小王\t28\t男")
}

void sort_users_by_age(struct User users[]) {
  // 按照年龄从小到大排序users
}

void format_user_file(char* origin_file_path, char* new_file_path) {
  // open files...
  struct User users[1024]; // 假设最大1024个用户
  int count = 0;
  while(1) { // read until the file is empty
    struct User user = parse_to_user(line);
    users[count++] = user;
  }
  
  sort_users_by_age(users);
  
  for (int i = 0; i < count; ++i) {
    char* formatted_user_text = format_to_text(users[i]);
    // write to new file...
  }
  // close files...
}

int main(char** args, int argv) {
  format_user_file("/home/zheng/user.txt", "/home/zheng/formatted_users.txt");
}

使用Java语言编写的面向对象风格的代码:

public class User {
    private String name;
    private int age;
    private String gender;

    public User(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public static User praseFrom(String userInfoText) {
        // 将text(“小王&28&男”)解析成类User
    }

    public String formatToText() {
        // 将类User格式化成文本("小王\t28\t男")
    }
}

public class UserFileFormatter {
    public void format(String userFile, String formattedUserFile) {
        // Open files...
        List users = new ArrayList<>();
        while (1) { // read until file is empty 
            // read from file into userText...
            User user = User.parseFrom(userText);
            users.add(user);
        }
        // sort users by age...
        for (int i = 0; i < users.size(); ++i) {
            String formattedUserText = user.formatToText();
            // write to new file...
        }
        // close files...
    }
}

public class MainApplication {
    public static void main(Sring[] args) {
        UserFileFormatter userFileFormatter = new UserFileFormatter();
        userFileFormatter.format("/home/zheng/users.txt", "/home/zheng/formatted_users.txt");
    }
}

从上面的代码中, 我们可以看出, 面向过程和面向对象最基本的区别就是, 代码的组织方式不同.面向过程风格的代码被组织成了一组方法集合及其数据结构, 方法和数据结构的定义是分开的.面向对象风格的代码被组织成一组类, 方法和数据结构被绑定到一起, 定义在类中.

3.2面向对象编程相比面向过程编程的优势

  1. 对于大规模复杂程序的开发, 程序的处理流程并非单一的一条主线, 而是错综复杂的网状结构.面向对象编程比起面向过程编程, 更加能够应对这种复杂类型的程序开发.
  2. 面向对象编程相比面向过程编程, 具有更加丰富的特性(封装, 抽象, 继承, 多态).利用这些特性编写出来的代码, 更加易复用, 易扩展, 易维护.
  3. 面向对象编程语言比起面向过程编程语言, 更加人性化, 更加高级, 更加智能.

3.3看似面向对象编程实际是面向过程编程风格的代码

  1. 滥用getter, setter方法.

    面向对象封装的定义是: 通过访问权限控制, 隐藏内部数据, 外部仅能通过类提供的有限的接口访问, 修改内部数据.暴露不应该暴露的setter方法, 明显违反了面向对象编程的封装特性, 数据没有访问权限控制, 任何代码都可以随意修改它, 代码就退化成了面向过程编程风格的.

    在设计实现类的时候, 除非真的需要, 否则不要给属性定义setter方法.除此之外, 尽管getter方法相对setter方法要安全一些, 但是如果返回的是集合容器, 也要防范集合内部数据被修改的风险.

  2. 滥用全局变量和全局方法.

    在面向对象编程中, 常见的全局变量有单例类对象, 静态成员变量, 常量等, 常见的全局方法有静态方法.单例类对象在全局代码中只有一份相当于一个全局变量.静态成员变量归属于类上的数据, 被所有的实例化对象所共享, 在一定程度上也相当于全局变量.而常量是一种非常常见的全局变量, 比如一些代码中的配置参数, 一般都设置为常量, 放到一个Constants类中.

    静态方法一般用来操作静态变量或者外部数据, 一般放到各种Utils类中, 可以在不用创建对象的情况下, 直接拿;来使用.静态方法将方法与数据分离, 破坏了封装特性, 是典型的面向过程风格.

    对于Constants类和Utils类的设计, 尽量做到职责单一, 定义一些细化的小类, 比如MysqlConstants, RedisConstants, FileUtils, IOUtils, StringUtils, UrlUtils, 而不是定义一个大而全的Constants类, Utils类.除此之外, 如果能将这些类中的属性和方法, 划分归类到其他业务中, 那是最好不过的了, 能极大地提高类的内聚性和代码的可复用性.

  3. 基于贫血模型的开发模式

    基于MVC三层结构的Web后端开发中, 传统的MVC结构分为Model层, Controller层, View层这三层, 而在将前后端分离之后, 三层结构在后端开发中, 被分为Controller层, Service层, Repository层.Controller层负责暴露接口给前端调用, Service层负责核心业务逻辑, Repository层负责数据读写.而在每一层中, 我们又会定义相应的VO(View Object), BO(Business Object), Entity.一般情况下, VO, BO, Entity中只会定义数据, 不会定义方法, 所有操作这些数据的业务逻辑都定义在对应的Controller类, Service类, Repository类中.这就是典型的面向过程的编程风格.

    实际上, 这种开发模式叫做基于贫血模型的开发模式, 是现在非常常用的一种Web项目的开发模式.

4.接口v.s.抽象类

在面向对象编程中, 抽象类和接口是两个经常被用到的语法概念, 是面向对象四大特性, 以及很多设计模式, 设计思想, 设计原则编程实现的基础.比如, 我们可以使用接口来实现面向对象的抽象特性, 多态特性和基于接口而非实现的设计原则, 使用抽象类来实现面向对象的继承特性和模板设计模式等等.

不过, 并不是所有的面向对象编程语言都支持这两个语法概念, 比如, C++ 这种编程语言只支持抽象类, 不支持接口;而像 Python 这样的动态编程语言, 既不支持抽象类, 也不支持接口.

4.1 抽象类和接口的语法特性

抽象类不允许被实例化, 只能被子类继承.抽象类可以包含属性和方法, 方法既可以包含代码实现, 也可以不包含代码实现, 不包含代码实现的方法叫做抽象方法.子类继承抽象类, 必须实现抽象类中的所有抽象方法.

接口不能包含属性, 只能声明方法, 方法不能包含代码实现.类在实现接口的时候, 必须实现接口中声明的所有方法.

4.2 抽象类和接口的存在意义

抽象类是对成员变量和方法的抽象, 表示一种is-a关系, 是为了解决代码复用问题.

接口是对方法的抽象, 是一种has-a关系, 表示具有某一组行为特性, 是为了解决解耦问题, 隔离接口和具体的实现, 提高代码的扩展性.

4.3 抽象类和接口的应用场景

如果要表示一种is-a关系, 并且是为了解决代码复用问题, 就使用抽象类;如果要表示一种has-a关系, 并且是为了解决抽象而非代码复用问题, 就使用接口.

5.基于接口而非实现编程

5.1 基于接口而非实现编程

基于接口而非实现编程, “Program to an interface, not an implementation”.从本质上来看, 接口就是一组“协议”或者“约定”, 是功能提供者提供给使用者的一个“功能列表”.

“基于接口而非实现编程”, 也叫“基于抽象而非实现编程”.越抽象, 越顶层, 越脱离具体某一实现的设计, 就越能提高代码的灵活性, 越能应对未来的需求变化.好的代码设计, 不仅能应对当下的需求, 而且在将来需求发生变化的时候, 仍然能够在不破坏原有代码设计的情况下灵活应对.而抽象就是提高代码扩展性, 灵活性, 可维护性最有效的手段之一.

5.2 具体做法

  1. 函数的命名不能暴露任何实现细节
  2. 封装具体的实现细节
  3. 为实现类定义抽象的接口.具体的实现类都依赖与统一的接口定义, 遵从一致的协议.使用者依赖接口, 而不是具体的实现来编程.

总之, 要有抽象意识, 封装意识, 接口意识.在定义接口的时候, 不要暴露任何实现细节.接口的定义只表明做什么, 而不是怎么做.而且, 在设计接口的时候, 要多思考一下, 这样的接口设计是否足够通用, 是否能够做到在替换具体的接口实现的时候, 不需要任何借口定义的改动.

5.3 防止过度使用

“基于接口而非实现编程”的设计初衷是, 将接口与实现相分离, 封装不稳定的实现, 暴露稳定的接口.上游系统面向接口而非实现编程, 不依赖不稳定的实现细节, 这样当实现发生变化的时候, 上游系统的代码基本上不需要做改动, 以此来降低代码间的耦合性, 提高代码的扩展性.

从设计原则的初衷来看, 如果在我们的业务场景中, 某个功能只有一种实现方式, 未来也不可能被其他实现方式替换, 那我们就没有必要为其设计接口, 也没有必要基于接口编程, 直接使用实现类就可以了.

除此之外, 越是不稳定的系统, 我们越要在代码的扩展性, 维护性上下功夫.相反, 如果某个系统特别稳定, 在开发完之后, 基本上不需要做维护, 那我们就没有必要为其扩展性, 投入不必要的开发时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值