设计原则&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个方面
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
- 子类中可以增加自己特有的方法
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要与父类一致或更严格
🔽 正确示例(规则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类图说明
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
三、设计模式分类
设计模式分为三大类:
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式;
结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式;
行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式;