产品经理吭哧吭哧跑过来:“xxx,我有个点子,balabalalbal,这么做很简单吧,就是一个小功能”。
程序员小强:“啊呀,这个功能要改不少代码啊。balabalabala 你看,这么多类都要改,要1周时间”。
产品经理:“不行啊,我们这个很关键,最好今天就上。或者要不明天早上?”
程序员小强:“滚😡”

本猿就遇到过这样子的情况,也许我们是维护的是一堆老代码,亦或者是自己给自己挖的坑。其实我们在拿到产品需求开始敲代码时,应该先仔细想想,到底应该怎么去做,然后又有哪些原则应该去遵循。

1. OO设计的基础

1.1 抽象与继承

抽象就是对我们代码中需要实体化的对象进行归纳抽象,找出共同点与不同点。比如书中说“游戏中会出现各种鸭子,有绿头鸭,有红头鸭,一边游泳戏水,一边呱呱叫”。在这,我们就可以抽象出一个“鸭子(Duck)”的基类出来。而子类,则为“绿头鸭(MallardDuck)”,“红头鸭(RedheadDuck)”。子类和基类之间则是继承关系。

1.2 多态 or 封装?

某天一早上产品经理来了:“我们的鸭子都要会飞(行为)”,这个时候一般来说很多人就想简单嘛,基类里加一个“飞(Fly)的方法”。

  1. class Duck {
  2. void fly();
  3. }
  4. class MallardDuck extends Duck

过了一上午,产品经理又来了:“哦,我觉得我们还需要加上一些橡皮鸭,它不会飞(行为),也不会叫(行为),他是橡皮(类型)做的”。
很多时间我们大脑的第一反应就是多态嘛,**橡皮鸭覆盖飞(Fly方法。有一定经验的朋友们可能会想到,这是个无底洞,在给自己挖坑,如果又要加一个鸭子类型,然后忘记覆盖飞的方法了怎么办?

接口(Interface)是否可以?

  1. class MallardDuck implements Flyable

也许可以,但是还有一个问题,如果再加一个叫(Quack)的行为呢,而且每种鸭子叫的行为不一样? 那我们的代码就变成了:

  1. class MallardDuck implements Flyable, Quackable

看起来,好像也很糟糕😰,接口里的每个子类方法都要实现一遍

设计原则:1. 把会变的地方“封装”起来,好让其他部分不受到影响。

行为:飞(Fly),叫(Quack)

设计原则:2. 针对接口编程,而不是针对实现(针对基类(超类型)编程)

行为放入专门的类中,此类只提供行为接口的实现。这样基类就不要知道行为的实现细节。**

  1. interface FlyBehavior{}
  2. class FlyWithWings implements FlyBehavior {
  3. fly() {}
  4. }
  5. class FlyWithNoWings implements FlyBehavior {
  6. fly() {}
  7. }

这样一来,这些行为接口类也可以被其他类复用,比如鸟(Bird)类。

1.3 整合

委托(delegate):鸭子(Duck)将飞行(Fly)和叫(Quack)的行为委托给别的类(Behavior)处理。

  1. class Duck {
  2. FlyBehavior flyBehavior;
  3. QuackBehavior quackBehavior;
  4. }
  5. class MallardDuck extendsw Duck {
  6. public MallardDuck() {
  7. flyBehavior = new FlyWithWings();
  8. }
  9. }

这个时候,我们发现,好像违反了设计原则2。那我们改怎么做么?

1.4 动态设定

  1. class Duck {
  2. FlyBehavior flyBehavior;
  3. QuackBehavior quackBehavior;
  4. public void setFlyBehavior(FlyBehavior fb) {
  5. flyBehavior = fb;
  6. }
  7. }

2. OO设计的原则

总结一下:我们先抽象,再找关系HAS-A,IS-A,IMPLEMENTS