一、面对对象

一、面对对象

主流的编程风格有三种,它们分别是面向过程、面向对象和函数式编程

面向对象的四大特性:封装、抽象、继承、多态

封装

访问权限控制,隐藏信息、保护数据

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据    
好处:  
1、能够减少耦合  
2、类内部的结构可以自由修改  
3、可以对成员进行更精确的控制  
4、隐藏实现细节

抽象

隐藏方法的具体实现

继承

代码复用;
表示类之间的 is-a 关系

使用已存在的类定义作为基础建立新类的技术,能方便的复用以前的代码,提高开发效率  
1、子类拥有父类非private的属性和方法  
2、子类可以拥有自己属性和方法,子类对父类进行扩展  
3、子类可以用自己的方式实现父类的方法    

构造器  
对于构造器而言,只能被调用,不能被继承,构建过程是从父类向子类扩散  

protected关键字  
任何继承该类的子类或者其他任何位于同一个包的类,才可以访问  

向上转型  
将子类转换成父类,总是安全的,唯一发生变化的可能是属性和方法的丢失  

继承缺陷:  
1、父类变,子类就必须变  
2、继承破坏了封装,对于父类而言,它的实现细节对于子类都是透明的  
3、继承是一种强耦合关系

如果必须向上转型,则继承是必要的,但是如果不需要,则应当考虑是否需要继承  
过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差

多态

一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法到底是哪个类中实现的方法,必须由程序运行期间才能决定。

指向子类的父类引用由于向上转型,它只能访问父类中拥有的方法和属性,对于子类中存在而父类中不存在的方法,该引用不能使用。  
若子类重写了父类中的某些方法,在调用该方法的时候,必定是使用子类中定义的这些方法。

继承链中对象方法的调用优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

面向对象编程与面向过程编程的区别和联系

  • 区别

面向对象编程以类为组织代码的基本单元,面向过程编程则是以过程(或方法)作为组织代码的基本单元。

它最主要的特点就是数据和方法相分离。相较于面向对象编程语言,面向过程编程语言最大的特点就是不支持丰富的面向对象编程特性,比如继承、多态、封装


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

    对于大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。

    面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。

    从编程语言跟机器打交道的方式的演进规律中,我们可以总结出:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能

  • 三种违反面向对象编程风格的典型代码设计

  1. 滥用 get 、set 方法

违反了面向对象编程的封装特性

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

Utils 类,只包含静态方法不包含任何属性的 Utils 类

Constants 类,会影响代码的可维护性;当 Constants 类中包含很多常量定义的时候,依赖这个类的代码就会很多。那每次修改 Constants 类,都会导致依赖它的类文件重新编译

影响代码的复用性;即便类只依赖 Constants 类中的一小部分常量,我们仍然需要把整个 Constants 类也 一并引入,也就引入了很多无关的常量到新的项目中

  1. 定义数据和方法分离的类 (基于贫血模型的开发模式)

数据定义在一个类中,方法定义在另一个类中;基于 MVC 三层结构做 Web 方面的后端开发

面向对象分析、面向对象设计、面向对象编程

1、面向对象分析(OOA)

需求分析的过程实际上是一个不断迭代优化的过程,先给出一个粗糙的、基础的方案,有一个迭代的基础,然后再慢慢优化

2、面向对象设计(OOD)

1、划分职责进而识别出有哪些类;

根据需求描述,我们把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类

2、定义类及其属性和方法;

识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选

3、定义类与类之间的交互关系;

泛化、实现、关联、聚合、组合、依赖

4、将类组装起来并提供执行入口。

3、面向对象编程(OOP)

接口和抽象类的区别以及各自的应用场景

  • 抽象类
    一种 is-a 的关系。
    抽象类更多的是为了代码复用

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

  • 接口
    一种 has-a 关系,表示具有某些功能
    接口是对行为的一种抽象,侧重于解耦

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

基于接口而非实现编程的设计思想

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



1、函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()。


2、封装具体的实现细节。比如,跟阿里云相关的特殊上传(或下载)流程不应该暴露给调用者。我们对上传(或下载)流程进行封装,对外提供一个包裹所有上传(或下载)细节的方法,给调用者使用


3、为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程。

多用组合少用继承的设计思想

破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑


继承层次过深、过复杂,也会影响到代码的可维护性;


破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑


一方面,增加了编码的工作量;另一方面,也违背了最小知识原则,暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。

将“鸟类”这样一个抽象的事物概念,定义为一个抽象类 AbstractBird,定义一个 fly() 方法
鸵鸟就不会飞。鸵鸟继承具有 fly() 方法的父类,就会出现错误

利用组合、接口、委托三个技术手段,解决继承存在的问题

针对“会飞”这样一个行为特性,我们可以定义一个 Flyable 接口,只让会飞的鸟去实现这个接口。
对于会叫、会下蛋这些行为特性,我们可以类似地定义 Tweetable 接口、EggLayable 接口


针对三个接口再定义三个实现类,它们分别是:实现了 fly() 方法的 FlyAbility 类、实现了 tweet() 方法的 TweetAbility 类、实现了 layEgg() 方法的 EggLayAbility 类。 然后,通过组合和委托技术来消除代码重复

public interface Flyable {
     
     
  void fly();
}
public class FlyAbility implements Flyable {
     
     
  @Override
  public void fly() {
     
      
      //... 
  }
}
// 省略 Tweetable/TweetAbility/EggLayable/EggLayAbility
public class Ostrich implements Tweetable, EggLayable {
     
     // 鸵鸟
  private TweetAbility tweetAbility = new TweetAbility(); // 组合
  private EggLayAbility eggLayAbility = new EggLayAbility(); // 组合
//... 省略其他属性和方法...
  @Override
  public void tweet() {
     
     
      tweetAbility.tweet(); // 委托
  }
  @Override
  public void layEgg() {
     
     
      eggLayAbility.layEgg(); // 委托
  }
}

继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用


is-a 关系,我们可以通过组合和接口的 has-a 关系来替代
多态特性我们可以利用接口来实现
代码复用我们可以通过组合和委托来实现

  • 如何判断该用组合还是继承

如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。


反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。


除此之外,还有一些设计模式会固定使用继承或者组合。比如,装饰者模式、策略模式、组合模式等都使用了组合关系,而模板模式使用了继承关系

面向过程的贫血模型和面向对象的充血模型

基于贫血模型的 MVC 架构

MVC 三层架构开发模式,违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格


像 POJO 这样,只包含数据,不包含业务逻辑的类,就叫作贫血模型(Anemic Domain Model)。这种贫血模型将数据与操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。


基于贫血模型的传统开发模式中,Service 层包含 Service 类和 BO 类两部分,BO 是贫血模型,只包含数据,不包含具体的业务逻辑,业务逻辑集中在 Service 类中

基于充血模型的 DDD 开发模式

充血模型(Rich Domain Model),数据和对应的业务逻辑被封装到同一个类中。这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。

  • 领域驱动设计

领域驱动设计,即 DDD,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。


微服务除了监控、调用链追踪、API 网关等服务治理系统的开发之外,还有另外一个更加重要的工作,那就是针对公司的业务,合理地做微服务拆分。而领域驱动设计恰好就是用来指导划分服务的。所以,微服务加速了领域驱动设计的盛行。


基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式的区别主要在 Service 层


基于充血模型的 DDD 开发模式中,Service 层包含 Service 类和 Domain 类两部分。Domain 就相当于贫血模型中的 BO。不过,Domain 与 BO 的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑,而 Service 类变得非常单薄。

  • 在基于充血模型的 DDD 开发模式中,将业务逻辑移动到 Domain 中,Service 类在这种情况下担当的职责是什么?哪些功能逻辑会放到 Service 类中?

1.Service 类负责与 Repository 交流,Service 类负责与 Repository 层打交道,调用 Repository 类的方法,获取数据库中的数据,转化成领域模型,然后由领域模型来完成业务逻辑,最后调用 Repository 类的方法,将数据存回数据库。
2.Service 类负责跨领域模型的业务聚合功能。
3.Service 类负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC 接口等,都可以放到 Service 类中。

猜你喜欢

转载自blog.csdn.net/weixin_46488959/article/details/126918918
今日推荐