面向对象的程序设计
原文链接 https://vaniot-s.github.io/2018/04/16/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
面向对象编程(Object Oriented Programming简称OOP)是一种计算机编程架构,程序开发方法的实践。面向对象将对象作为程序的基本单位,程序和数据封装于其中。两个基础的概念是类
与对象
,类与对象的关系是模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类。类描述了一组有相同特性(属性)和相同行为(方法)的对象。
面向对象的实现了三个目标,重用性、灵活性和扩展性,使系统的各个部分分工明确。使编程的代码更简洁、更易于维护。
三个基本特征
面向对象编程,有三个基本的特征:封装,继承,多态。
- 封装隐藏实现的细节,代码模块化。
- 继承扩展已存在的代码模块。 > 封装和继承实现了代码的重用。
- 多态在类的继承和派生的时候,保证了类的实例的某一属性的正确调用,多态实现了接口的重用。 <!--more--> ### 抽象与封装 抽象把系统中需要处理的数据及操作结合在一起,成为不同的抽象数据类型。封装利用类的访问控制把自己的数据(信息)和方法(动作)只让可信的类或者对象操作,达到对不可信的类或者对象进行信息隐藏的效果。 ### 继承 继承是创建新的类(子类,派生类)使用已有类(父类,基类,超类)的所有功能,在类与类之间建立了关系。类的继承是从一般到特殊的过程,实现代码的重用。在无需重新编写父类的情况下对这些功能进行扩展。
- 接口继承:仅使用属性和方法的名称、但是子类必须提供实现的能力; > 聚合:逻辑上B是A的一部分(A part of),则不允许从B派生A,B与A的其他部分组合出组合出A。
- 聚合(共享):相对松散的关系,聚合类B不需要对被聚合的类A负责
- 组合(复合):has-a,A的生命周期受到B的控制,A会随着B的创建而创建。
- 依赖:B依赖A,如果A被修改,那么B类会受到影响。
- 实现继承:使用基类的属性和方法而无需额外编码的能力;
- 可视继承:指子窗体(类)使用基窗体(类)的外观和实现代码的能力
泛化(Generalization):逻辑上若B是A的一种(a kind of),则允许B继承A的功能和属性,A为B的基类,若B与A毫不相干,则不能为了B拥有更多的功能而继承A的功能和属性。
多态
根据使用类的上下文重新定义或改变类的性质和行为,系统根据不同情况调用相应不同的方法,实现不同的功能。实现多态的两种方式:
- 覆盖:重新定义父类虚函数的做法
- 重载: 允许存在多个同名函数,而这些函数的参数表不同
ps:实际上重载与多态无关,而只是一种语言的特性
设计原则(SOLID)
单一职责原则(single responsibility principle)
类应仅具有单一职责,避免将相同的职责分散到不同的类中,且避免一个类承担太多的职责(符合"高内聚,低耦合"的思想)。对于某个类的职责应站在其它类的角度来定义,职责包含许多相关的功能,即一个类只负责一组相关的事。 单一职责的优点:
- 减少类之间的耦合
- 提高类的复用性,将一系列的职责组件,便于使用。
工厂模式,命令模式,代理模式体现了单一职责。
开放封闭原则(Open-Closed principle)
开放封闭原则可提高系统的可扩展性和可维护性,即对于一个模块而言:
- 开放:模块的行为必须是开放的,支持扩展。
- 封闭:对于模块功能扩展时,不应该影响或大规模的影响已有的程序模块。
开放封闭的思想:对抽象编程,不对具体编程,抽象的特点是具有相对的稳定性。修改则是封闭的,指类依赖于固定的抽象。扩展是开放的,指通过面向对象的继承和多态,实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法。
- OCP在设计时遵循"抽象"和"封装"的思想,在软件系统中将各种"可变因素"封装,且一种可变因素不应当散落在各个不同的代码块中,而是在一个对象中。
- 在系统功能编程实现方面应用面向接口编程,当需求发生变化时,提供接口新的实现类,面向接口编程要求功能实现接口,对象声明为接口类型。
设计模式中装饰者模式较明显的体现了OCP
里氏替换原则(Liskov Substituion Principle)
继承的子类必须要能够替换它的父类型,并出现在父类型能够出现的任意地方。即子类可以扩展父类的功能,但不能改变父类原有的功能。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类可以增加自己特有的方法
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
里氏替换原则的设计规范:
- 父类方法都要在子类中实现或重写,并且派生类只实现其抽象类中声明的方法,而不应当给出多余的方法定义或实现
- 在客户端程序程序中应只使用子类对象,实现运行期绑定(动态绑定)
接口隔离原则(Interface-Segregation Principle)
接口能有效的将细节和抽象隔离,但类不应当依赖不需要的接口,应当使用多个专门的接口,而不是单一臃肿的总接口,类不用去实现不需要的方法。
接口污染的两种处理方式:
- 委托分离:通过增加新的类型来委托程序的请求(设计模式中的策略模式,代理模式)。
- 多重继承分离,通过接口多继承实现程序的需求。
依赖倒置原则(Dependecy-Inversion Principle)
一个方法应该遵从"依赖于抽象而不是一个实例",依赖倒置的核心原则是解耦,依赖倒置的概念:
- 高层模块(复杂的业务逻辑)不应依赖低层模块(执行原子操作)。两者都应都应该依赖于其抽象
- 抽象不应该依赖细节
- 细节应该依赖于抽象
抽象:即抽象类或接口,两者是不能够实例化的。
细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化。
对于依赖倒置的实现应遵循:
- 每个较高层次类都为其所需要的服务提供一个接口声明,较低的层次类实现接口
- 每个高层次类都通过该抽象接口使用服务
迪米特原则( Law of Demeter)
一个对象应当对其他对象尽可能少的了解,类之间的关系越密切,耦合度越大,类发生改变时,对另一个类的影响也越大。