一、面对对象

一、面对对象

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

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

封装

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

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据    
好处:  
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 类中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值