面向对象的程序设计

2018-04-16 Vaniot 更多博文 » 博客 » GitHub »

面向对象

原文链接 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)是一种计算机编程架构,程序开发方法的实践。面向对象将对象作为程序的基本单位,程序和数据封装于其中。两个基础的概念是对象,类与对象的关系是模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类。类描述了一组有相同特性(属性)和相同行为(方法)的对象。

面向对象的实现了三个目标,重用性、灵活性和扩展性,使系统的各个部分分工明确。使编程的代码更简洁、更易于维护。

三个基本特征

面向对象编程,有三个基本的特征:封装,继承,多态。

  • 封装隐藏实现的细节,代码模块化。
  • 继承扩展已存在的代码模块。 > 封装和继承实现了代码的重用。
  • 多态在类的继承和派生的时候,保证了类的实例的某一属性的正确调用,多态实现了接口的重用。 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)

一个对象应当对其他对象尽可能少的了解,类之间的关系越密切,耦合度越大,类发生改变时,对另一个类的影响也越大。