设计原则和思想:面向对象-理论篇

设计原则和思想:面向对象-理论篇

一、什么是面向对象

  1. 面向对象编程和面向对象编程语言概念
    面向对象编程:简称之为OOP(Object Oriented Programming),它有两个非常重要的基础概念,即类(class)和对象(Object)。它是一种编程范式或者编程风格,以类或者对象作为组织代码的基本单元,并将封装、抽象、继承、多态四种特性作为代码设计和实现的基石。
    面向对象编程语言:是一种支持类和对象的语法机制,方便实现四种特性的语言。
    两者之间没有必然联系,面向对象编程不一定要用面向对象编程语言,面向对象编程语言也不一定实现面向对象编程。
  2. 如何评判是否是面向对象编程语言
    其实关于如何评判是不是面向对象编程语言,并没有官方的定义。如果不按严格定义,现在大部分编程语言都属于面向对象编程语言,因为它们都是以类或对象作为代码组织的单元,但是并不一定支持四大特性。所以如果严格定义的话,很多又都不属于面向对象编程语言。主要是四大特性是在漫长的编程历史中总结出来,能够很好去实现各种面向对象的设计思路。所以,个人觉得可以粗略认为,只要某种编程语言,支持类或者对象的语法概念,并以此作为代码组织的基本单元,都可以认为是面向对象编程语言。
  3. 什么是面向对象分析和面向对象设计
    面向对象分析,简称OOA(Object Oriented Analysis);
    面向对象设计,简称OOD(Object Oriented Design)。
    其实就是对类或者对象的分析设计,比其他分析设计更接近编码,更容易过渡到面向对象编程阶段。

二、封装、继承、多态、抽象能解决哪些编程问题

对于四大特性,不同的面向对象编程语言会提供特定的语法机制支持,但是不同的语言,语法机制有区别。所以我们回归特性本身,屏蔽语法机制的差异性。下面研究四大特性的目的和作用
  1. 封装(Encapsulation)
    封装定义:其实也叫信息隐藏和数据保护,类通过对外暴露有限的接口,授权外部只能通过类提供的有限的接口(函数)访问类的信息或数据。
    当然对于封装特性,需要面向对象编程语言提供一定的语法机制支持,就是权限控制,比如java中的private、publish等,如果没有权限控制,都默认是publish,那封装就没有了意义,外界可以随意修改类的数据,无法保护数据。
    封装意义:假设我们不做权限控制,那谁都可以去修改类中的数据,会产生各种奇葩的修改,那就会造成不可控。封装还可以提高易用性,外界只需要通过有限暴露接口去做必要的操作,屏蔽复杂的业务逻辑,这样其他调用者不需要理解太多业务,只需要关注自己需要的操作即可,这样避免数据不一致性。
  2. 抽象(Abstraction)
    抽象定义:抽象主要是对方法实现的抽象,隐藏实现细节,调用者只需要知道该方法的作用,而不需要知道方法是怎么实现的。不仅抽象类的方法可以看作抽象方法,普通的函数也可以看作抽象的结果,因为函数包含了实现细节。
    抽象作用: 当程序复杂度高时,人脑需要记忆的东西就太过繁杂,这个时候我们就需要简化,减轻我们记忆负担,这个过程就是抽象,只需要抽象出重要的信息,屏蔽掉不重要 的细节。其实很多设计都是基于抽象思想,比如基于接口而非实现的编程方式,开闭原则,解藕等。在我们对类的方法命名时,也要有抽象思想,不要暴露太多信息。
  3. 继承(Inheritance)
    继承定义:继承是指某一类的属性,在另一类中体现到。比如猫属于动物,那么我们就可以抽象出动物的共有属性,让猫等其他动物继承这些属性。
    继承作用:继承可以做到代码重复利用,相同的属性抽象出来,让子类去继承。但是过度使用继承,会加大代码复杂度,耦合度,父类的修改直接会影响到子类的功能。我们尽可能用组合而不用继承,就是解藕。
  4. 多态(Polymorphism)
    多态定义: 多态是指子类可以替换父类,在运行时候,去调用子类的实现方法。主要分为继承多态,接口多态。继承多态要求语法支持继承、父类可以调用子类方法、子类可以重写方法机制,因为Java是静态语言,必须要有继承关系;接口多态要求类必须要实现接口。
    多态作用: 多态作用是提高代码的拓展性和复用性,不同的类去实现接口,或者继承抽象类,然后重写该方法就可以实现了代码的复用性。如果没有多态,那就要每个类都需要写不同的方法去实现该功能。是很多模式实现的基石。

三、面向对象比面向过程优势

  1. 面向过程编程:面向过程编程是一种简单编程风格,是一种线性编程,主要关注的是方法函数还有执行顺序,数据和方法是分开的,没有面向对象提供的那么多特性,数据是可以被函数随意更改的。因为是一种线性风格,所以在面对复杂的网状业务时,需要分析各种执行主线,就没有面向对象的优势大,面向对象只需要将复杂的业务,用类将其关系连接起来即可相互联系。
  2. 面向对象:是一种更高级的语言,更接近人类自然远离机器的语言。提供更加丰富的特性,面向对象风格代码更加易于拓展,维护。数据和方法强制绑定一起,更容易形成模块化。

四、似是面向对象,实是面向过程?

  1. 滥用getter、setter方法
    在定义类的时候,我们一般会定义一些属性,而且使用的是私有权限,但是有时候我们还定义了所有属性的getter、setter方法,这样等于将属性的权限开放,任何地方都可以随时修改属性值。有些属性不能被修改,我们不能将其定义,这样不符合面向对象封装特性,封装的定义是通过访问权限控制,隐藏内部数据,并通过有限的方法去访问。
  2. 滥用全局变量和全局方法
    常见的全局变量有单例的对象、静态成员变量、常量等。常见的情况是将所有的变量都定义在一个类中,比如常量类,这样会有很多类去依赖这个常量类,当类成员过多时,加载会变的很慢,最好的方法是将类需要用到的常量直接定义在该类上,或者分类去定义。工具类是我们很常用的类,但是工具类是彻彻底底的面向过程方式,作用是避免代码重复,比如多个类要用到同个方法,但是他们又不同属性,不能抽取出来父类,所以工具类是最好的方法。但是我们尽量避免,可以考虑在某个类中定义其方法来复用。
  3. 定义数据和方法分离的类
    在面向过程编程时候最常用的方式就是数据和方法分离。但是实际上在面向对象中成为贫血式开发模式。比如我们定义的VO、BO、PO类等。
总结:为什么我们很容易在面向对象编程时候写出面向过程的代码?

在我们日常生活中,每次要完成一件事情,我们总是会思考先完成那一步到那一步,这是一个日常生活逻辑,很符合面向过程方式。而面向对象,其实是比较复杂的编程方式,因为面向对象是考虑每个类的设计,然后将各个类进行组装成复杂的程序。所以在一些微小程序中,我们可以考虑更简单的面向过程方式,但是要避免其弊端。

五、接口和抽象类的区别?如何用普通的类模拟抽象类和接口?

  1. 什么是抽象类和接口?区别在哪里?

(1)抽象类

  • 抽象类是不允许实例化的,也就是不能new出来。
  • 抽象类可以定义属性和方法,没有实现的方法叫抽象方法。
  • 继承抽象类时,是要复写实现抽象类的所有抽象方法的。

(2)接口

  • 接口是不能定义属性的,也就是成员变量
  • 接口只能声明方法,不能实现方法
  • 实现接口的类,必须实现其所有声明的接口

区别是抽象类是一个特殊类,不能被实例化,但是可以定义属性和方法实现,也可以表示is-a关系。接口是一个has-a关系,表示有某些功能,有个经典叫法为协议。

  1. 抽象类和接口能解决的问题

(1)抽象类
抽象类主要解决的是复用问题,当两个子类有相同的方法时,可以抽取出来一个抽象父类抽象方法,跟普通父类的区别是抽象父类是一定要实现的抽象方法,还可以实现多态,是普通父类不能完成的,而且抽象类不能实例化,避免空方法被调用,误导使用者。
(2)接口
接口其实也就是一个协议或者契约,主要解决的是解藕问题,是对行为的一种抽象,实现了约定和实现的解藕,我们不需要了解实现,只需要知道该约定的作用就可以。提高代码易扩展性

  1. 怎么选用
  • 如果是解决复用问题,使用抽象类,如果是解决解藕问题,使用接口

六、为什么基于接口而非实现编程?有必要为每个类都定义接口吗?

  1. 如何解读原则中的“接口”
  • 接口是一组协议,是功能提供者给使用者的一组功能列表,实际上,基于接口而非实现编程另一种解读是基于抽象而非实现编程。这条原则可以使接口和实现分离,封装不稳定的实现,暴露稳定的接口,上游系统基于接口而非不稳定的实现类,当实现改变时,上游系统不需要做改动,以此来降低耦合度。
  • 越抽象、越顶层,越脱离具体的某一实现类,提高代码的灵活度,不仅能应对现有的需求,当未来需求改变时也能在不破坏现有代码的前提下实现。
  1. 如何将该原则应用到实践中
  • 比如一个图片上传接口,我们定义成uploadToAliyun(),但是当有一天我们需求改变,要上传到私服,那该接口就不能再使用,这样会误导使用者。所以接口我们不能暴露我们的目的和细节。我们只需要知道这个接口是做什么就可以,不需要知道怎么做,可以直接定义为upload()。
  • 在做软件开发时,我们一定要有抽象意识,封装意识,接口意识。在定义接口时,不要暴露任何细节,只需要知道是做什么就可以,而且要考虑定义的接口是否通用。
  1. 是否需要为每个类定义接口?
    这个问题我们需要回归接口设计的初衷,接口设计主要是为了解决代码的耦合度,封装不稳定的实现,暴露稳定的接口,当需求改变时,上游系统不需要做需改。那么当我们的类是稳定的,未来也不会改变的,那就不需要去定义接口。

七、为何多使用组合少用继承?如何解决使用组合还是继承

  1. 为什么不推荐使用继承
    不推荐,是在一定场景下,比如鸟类,鸟类有很多特性,但是如果使用继承关系,那么就要延伸出很多分类不同的类去继承,层级过多,可读性
  2. 组合相对继承的优势
    对于繁杂的继承关系。我们可以用组合、接口、委托技术去解决。比如定义一个会飞的接口,一个会下蛋的接口,然后使用委托去实现该能力解决复用问题,最后根据具体的类去组合该类拥有的特性。比如麻雀,会飞,也会下蛋,鹰也一样,那我们不可能每个类中实现这两个能力,这样会造成代码重复,那么委托就可以解决该委托,我们只需要在具体类中引入该委托就可以。
  3. 如何判断使用继承还是组合
    对于简单稳定而且层级比较少的关系,我们可以使用继承。关系比较繁杂可以使用组合方式,因为组合方式需要定义很多类去完成。

八、实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?

我们很多业务系统都是基于MVC三层架构来开发的,切确来说这是一种基于贫血模型的MVC架构来开发的,虽然它已经形成了标准的MVC开发模式,但是它是违反了面向对象风格的,是一种彻彻底底的面向过程风格,所以很多人称之为“反模式”。特别是领域驱动设计(domain driver design)出来后,越来越被人诟病,而基于充血模式的DDD开发模式越来越被提倡。

  1. 什么是基于贫血模型的开发模式
    MVC三层架构我们都很熟悉(model、view、controller),当然很多时候在开发过程中不一定完全按照这个架构来,比如现在前后端分离,我们形成新的三层架构(Controller、Service、Repository),而且我们一般会定义BO、VO、PO之类的实体类,这些类只有数据,没有逻辑,逻辑全部放在Service中。这种类就叫贫血模型,这种贫血模型把数据和操作分离,破坏了封装特性,是一种典型的面向过程模式。
  2. 什么是基于充血模型的DDD开发模式
    在贫血模型中,数据和操作是分开的,而充血模型刚好相反,数据和操作是放在同一个类中,满足封装特性,是典型的面向对象模式。
  3. 什么是领域驱动设计
    主要是用来指导如何解藕业务,划分业务模块,定义业务领域模型以及其交互。有点类似敏捷开发的概念。基于充血模型的DDD开发模式是将domain和service放一起的,重domain轻service,贫血模型下是重service,轻POJO。
  4. 为什么基于贫血模型的开发模式这么受欢迎
  • 大多时候,我们的业务系统比较简单,所以我们不需要费脑去设计充血模型,贫血模型就足以应付,还有因为业务简单,设计出来的充血模型也很单薄,跟贫血模型差不多,意义不大。
  • 充血模型相对比较复杂,我们需要设计类,暴露哪些接口,定义哪些业务逻辑,而不像贫血模型,我们只需要定义数据,开发时候需要什么业务逻辑,直接去定义就可以。
  • 还有一个就是学习成本,更多时候,我们都会使用我们习惯用的东西。
  1. 什么项目我们需要考虑使用充血模型的DDD开发模式
    对于复杂的项目,我们更应该使用充血模型的DDD开发模式,贫血模式可以应付简单的业务系统,但是复杂的系统,贫血模式复用性很差,很多时候,业务逻辑没有多少,更多的是不能复用的SQL来完成的,对于复杂系统来说,这就会造成代码混乱,无法维护。所以对于复杂的系统,我们更应该将精力放在前期,了解整个业务需求基础上设计出充血模型,相当于可以复用的中间层代码。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值