设计原则&UML类图

一、六大原则

1.1、开闭原则

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简单来说:就是为了使程序的扩展性好,易于维护和升级

代码坏味道

class Car {
    public void run(Petrol petrol) {
        System.out.println("car use:" + petrol.type + ", run~");
    }
}
// 汽油
class Petrol {
    public Petrol(String type) {
        this.type = type;
    }
    // 汽油型号
    public String type;
}

public static void main(String[] args) throws Exception {
    Car car = new Car();
    car.run(new Petrol("93号汽油"));
}

上述代码Car依赖Petrol,是依赖关系,如果后续增加柴油,则需要对run()方法修改,明显违背了开闭原则,正确修改如下:

// 汽车
class Car {
    public void run(Oil oil) {
        System.out.println("car use :" + oil.type + ", run~");
    }
}
abstract class Oil {
    // 油型号
    protected String type;
}
// 汽油
class Petrol extends Oil{
    public Petrol(String type) {
        super.type = type;
    }
}
// 柴油
class DieselOil extends Oil{
    public DieselOil(String type) {
        super.type = type;
    }
}
public static void main(String[] args) throws Exception {
    Car car = new Car();
    // 不论是用什么油,都可以在不修改汽车类的基础上尽行变更
    car.run(new Petrol("96号汽油"));
    car.run(new DieselOil("69号柴油"));
}

1.2、接口隔离原则

使用多个隔离的接口,优于使用单个接口,降低类之间的耦合度,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。在开发过程当中回尽量地去降低依赖,降低耦合

1.3、迪米特法则

一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立

1.4、单一职责原则

单一职责的定义:应该有且只有一个原因引起类的变更。换句话说就是一个接口只做一件事,即一个职责一个接口。

但是困难的是划分职责时并没有一个标准,最终都是需要从实际的项目去考虑。我们在设计的时候,尽量单一,然后对于其实现类就要多方面的考虑。不能死套单一职责原则,否则会增加很多类,给维护带来不便

1.5、里氏代换原则

1、子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏

2、衍生类要正确的在基类的基础上增加新的行为

❓为什么一定要求子类能够替代父类呢?

在我们的日常开发中很常用依赖注入,简单来说就是通过外部传入对象而不是内部new对象。函数参数通常为父类或者接口,传入的只要是实现了这个接口的对象就可以了。这是就要必须保证传入的实现类不能破坏父类的执行流程,只有这样才能保证程序的正确性不被破坏。例如一个支付接口

int buy(Payment payment){
    payment.pay();
}

当传入Payment的实现类AliPayment、或者TencentPayment时,如果子类错误覆写写父类Payment的方法时,会造成buy()方法的行为被破坏,因此提出了里氏替换原则,其作用如下:

  • 里氏替换原则是实现开闭原则的重要方式之一
  • 它克服了继承中重写父类造成的可复用性变差的缺点
  • 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性
  • 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险

实现方法:通俗理解,只要有父类出现的地方,都可以用子类来替代,里氏替换原则对继承进行了规则上的约束,有以下4个方面

  1. 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
  2. 子类中可以增加自己特有的方法
  3. 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
  4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要与父类一致或更严格

🔽 正确示例(规则3)

public class A {
    public void fun(HashMap map) {
        System.out.println("父类被执行...");
    }
}

class B extends A {
    public void fun(Map map) {
        System.out.println("子类被执行...");
    }
}

// 子类的方法参数Map比父类的HashMap参数更宽松,满足里氏替换原则,只有父类的fun方法会被执行
public static void main(String[] args) {
    A a = new A();
    HashMap<Integer, Integer> map = new HashMap();
    a.fun(map); // 父类被执行...
    B b = new B(); //
    b.fun(map); // 父类被执行...
}

🔽 错误示例(规则3)

public class A {
    public void fun(Map map) {
        System.out.println("父类被执行...");
    }
}

class B extends A {
    public void fun(HashMap map) {
        System.out.println("子类被执行...");
    }
}

// 子类的方法参数Map比父类的HashMap参数更宽松,不满足里氏替换原则
// 两次调用fun(),分别执行了父类和子类的函数,引起了程序逻辑的混乱
public static void main(String[] args) {
    A a = new A();
    HashMap<Integer, Integer> map = new HashMap();
    a.fun(map); // 父类被执行...
    B b = new B(); //父类存在的地方,可以用子类替代,子类B替代父类A
    b.fun(map); // 子类被执行...
}

注:如果非要覆盖父类的非抽象,必须要遵循规则3或者考虑重新设计继承体系,才能保证不出错。如果是遵循第3点规则的话,其实子类中覆盖父类的方法永远都不会被调用到。

🅰️ 里氏替换原则不是不能override,而是要按照父类所期望的那样去override。这告诉我们,继承也是可以使用的,不过要熟悉相关说明。

1.6、依赖倒转原则

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

java中抽象指接口或抽象类,两者都不能直接被实例化的;细节就是实现类,实现接口或者集成抽象类而产生的也就细节,也就是可以可以加上一个关键字new产生的对象。高层模块就是调用端,低层模块就是具体实现类。

依赖倒置原则在java中表现就是,模块间依赖通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是通过接口或者抽象类产生的。

如果类与类直接依赖细节,那么就会直接耦合。如此一来当修改时,就会同时修改依赖者代码,这样限制了可拓展性

二、 UML类图说明

image

2.0 类图说明

Class类图:分为三层,第一层显示类名称,如果是抽象类则用斜体显示;第二层表示类的特性,通常是字段和属性;第三层是类的方法或者行为,前面的符号+表示public,-表示private,#表示protected,~表示default

Interface图:分为两层,第一层有<>标识,并显示接口名称;第二层表示接口方法

在面向对象程序设计时,类与类之间的关系主要分为继承,实现,依赖,关联,聚合,组合六种关系,空心菱形表示 contains of 关系,实心菱形表示 part of 关系

2.1 依赖关系

依赖关系用虚线、箭头表示,例如上图中的Animal依赖 Oxygen和Water,代码层面,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用;

abstract class Animal{
    public run(Oxygen oxygen, Water water) {}
}

2.2 关联关系

关联关系用实线、箭头表示,例如上图中的Penguin和Climate,表示类与类之间的联系,当一个类需要知道另一个类的属性和方法,可以使用关联,这种关系很强,比依赖关系更强,在代码层面和聚合关系一样,只能从语义上区分;

class Penguin extends Bird {
    private Climate climate;
}

2.3 组合关系

组合关系用实心菱形、实线、箭头表示,例如上图中的Bird和Swing是组合关系,代码层面,一个类是另外一个类的一部分(一个属性),强拥有关系,同生共死;常常会使用构造方法来达到组合目的; 比如:翅膀是鸟的一部分,轮胎是汽车的一部分,强调part of关系

class Bird {
    private Wing wing;
    public Bird(){
        wing = new Wing();
    }
}

2.4 聚合关系

聚合关系用空心菱形、实线、箭头表示,例如上图中的SwallowAggregate和Swallow是聚合关系,代码层面,和组合关系一样,一个类是另外一个类的一部分,不能互相拥有,是弱拥有关系例如:雁群和大雁,飞机场和飞机;强调 contains of关系

class WideGooseAggregate{
    private WideGoose[] arr;
}

2.5 泛化关系

泛化关系也叫继承关系,用空心三角形、实线表示,例如上图中Bird和Swallow、Duck、Penguin,代码层面extend

2.6 实现关系

实现关系用空心三角形、虚线表示,例如上图中Swallow和Fly接口,代码层面implements

三、设计模式分类

设计模式分为三大类:

创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式;

结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式;

行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式;

posted @ 2018-10-16 12:22  __Helios  阅读(424)  评论(0编辑  收藏  举报