【软考向】UML 关系、面向对象原则、设计模式定义

前置知识:UML 关系

在这里插入图片描述

  • 车的类图结构为 <<abstract>>,表示车是一个 抽象类

在 UML 类图中,抽象类 通常用 斜体字 表示,或在 类名前加上 <<abstract>> 标识。

  • 小汽车和自行车与车是 继承关系;更具体为 实现关系,使用 带空心箭头的虚线 表示。
  • 小汽车与 SUV 之间也是 继承关系,它们之间的关系为 泛化关系,使用 带空心箭头的实线 表示。

继承关系 为 is-a 的关系,两个对象之间如果可以用 is-a 来表示,就是继承关系:(…是…)
eg:自行车是车、猫是动物

实现关系(Realization)泛化关系(Generalization)

  • 泛化关系(Generalization):表示 类与类之间的继承关系,使用带空心三角箭头的实线表示。
  • 实现关系(Realization):表示 类实现接口 的关系,使用带空心三角箭头的虚线表示。
  • 小汽车与发动机之间是 组合关系,使用 带实心菱形箭头的实线 表示。
  • 学生与班级之间是 聚合关系,使用 带空心菱形箭头的实线 表示。

组合关系(Composition)聚合关系(Aggregation)

  • 组合关系表示 整体与部分的强依赖关系,使用 带实心菱形箭头的实线 表示。
    其中部分对象的生命周期完全依赖于整体对象,部分对象不能独立存在,部分对象不能被多个整体对象共享。

小汽车由发动机等部件组成,发动机不能脱离小汽车独立存在。

  • 聚合关系表示 整体与部分的弱依赖关系,使用 带空心菱形箭头的实线 表示。
    部分对象可以独立于整体对象存在,部分对象可以同时属于多个整体对象。

一个班级由多个学生组成,但学生可以在没有班级的情况下存在,或者转到其他班级。

  • 学生与身份证之间为 关联关系,使用 一根实线 表示。根据需要,可以添加箭头表示方向。
  • 学生上学需要用到自行车,与自行车是一种 依赖关系,使用 带箭头的虚线 表示。

关联关系(Association)依赖关系(Dependency)

  • 关联关系表示两个类之间存在某种 结构性的联系持久存在于类的整个生命周期中),一个类知道另一个类的属性和方法。使用 实线 连接两个类,可以根据需要添加箭头表示方向。

一个 Student 类有一个 IDCard 类型的 成员变量,表示 Student 与 IDCard 之间的关联关系。

  • 依赖关系表示 一个类在其实现中 临时使用 了另一个类,即一个类的变化可能会影响到另一个类的实现。使用 带箭头的虚线,箭头从依赖方指向被依赖方。

一个 Student 类的 goToSchool(Bicycle bike) 方法中 使用了 Bicycle 类的实例,表示 Student 依赖于 Bicycle。

什么是设计模式

“每一个模式描述了一个在我们周围 不断重复发生的问题,以及 该问题的解决方案的核心。这样,你就能 一次又一次地使用该方案而不必做重复劳动”。

—— Christopher Alexander

历史性著作《设计模式:可复用面向对象软件的基础》一书书系统地总结了 面向对象软件设计中的 23 种经典设计模式,旨在提供 可复用的解决方案

这些模式被分为三大类:

  • 创建型模式:关注对象的创建过程,封装了对象的实例化方式。
  • 结构型模式:关注类和对象的组合,旨在实现更大的结构。
  • 行为型模式:关注对象之间的通信和职责分配。

由埃里希·伽玛(Erich Gamma)、理查德·赫尔姆(Richard Helm)、拉尔夫·约翰逊(Ralph Johnson)和约翰·弗利西德斯(John Vlissides)四位作者合著,因而被称为 “四人帮”(Gang of Four,简称 GoF)

书名里有两个关键字,

  • 可复用:这是设计模式的目标,不能忘记这个目标呀!
  • 面向对象:是具体的方法。

软件设计复杂性的根本原因,就是 变化。如,客户需求的变化、技术平台的变化、市场环境的变化…

人们面对 复杂性 有两个常见的做法,

  • 分解:分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题;
  • 抽象:光分解是不够的。由于不能掌握全部的复杂对象,我们选择 忽视它的非本质细节,而去处理泛化和理想化了的对象模型。

分而治之的方法是不容易复用的,将来再来一种新的情况,就要再对它处理。比如,有 Line 类、Rect 类,再来一个 Circle 类,它们各自实现自己的 draw() 方法,但是如果是抽象成一个 Shape 类,其他 Line 类、Rect 类、Circle 类 符合这个 Shape 的抽象,那么就继承自它,这样就能对形状 统一处理

在这里插入图片描述

设计模式就像能 根据需求进行调整 的预制蓝图, 可用于解决代码中 反复出现的设计问题。模式并不是一段特定的代码, 而是解决特定问题的一般性概念。

人们常常会混淆 模式和算法, 因为两者在概念上都是 已知特定问题的典型解决方案。 但算法总是明确定义达成特定目标所需的一系列步骤, 而模式则是对解决方案的更高层次描述。 同一模式在两个不同程序中的实现代码可能会不一样。
算法更像是菜谱: 提供达成目标的明确步骤。而模式更像是蓝图: 你可以看到最终的结果和模式的功能, 但需要自己确定实现步骤。

从面向对象谈起

我们程序猿,作为人类需求和计算机的桥梁,和计算机的沟通叫底层思维,和人类需求的沟通叫抽象思维。

  • 底层思维向下,如何把握 计算机底层,比如语言构造、编译转换、内存模型、运行时机制
  • 抽象思维向上,如何将我们 周围的世界抽象为程序代码,比如 面向对象、组件封装、设计模式、架构模式。

在这里插入图片描述

  • 向下:深入理解 三大面向对象机制

    • 封装,隐藏内部实现;
    • 继承,复用现有代码;
    • 多态,改写对象行为。
  • 向上:深刻把握面向对象机制所带来的抽象意义,理解 如何使用这些机制来表达现实世界,掌握什么是 好的面向对象设计

面向对象设计原则

面向对象要能

  • 隔离变化。面向对象能把 相对稳定的部分 和 容易变化的部分 隔离,能够将变化带来的影响减到最小。比如上面的 Shape 是相对稳定的,而 Line、Rect 容易变化的被隔离起来。
  • 多态调用,各负其责。比如上面的 Shape 类,你是 Line 类呢,你就去画直线,自己画自己,就叫各负其责。

在这里插入图片描述

原则 1:依赖倒置原则(DIP)—— 面向抽象(接口)编程,而不是面向实现编程

在这里插入图片描述

  • 高层模块(相对稳定)不应该依赖于底层模块(容易变化),二者都 应该依赖于抽象(相对稳定)
  • 抽象(相对稳定)不应该依赖于实现细节(容易变化),实现细节应该依赖于抽象(相对稳定)

看下面这个代码,要用鼠标绘图,关注这个 MainForm 类,它依赖 Line 和 Rect 类,这违反了依赖倒置原则,

在这里插入图片描述 在这里插入图片描述

再来看看遵守依赖倒置原则的长什么样,MainForm 去依赖这个抽象类 Shape,而 Line 和 Rect 类继承 Shape,

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述

  • 抽象 就是指 接口或抽象类,两者都是不能直接被实例化的;
  • 细节 就是 实现类,实现接口或继承抽象类而产生的类就是细节。

再举个例子,人给动物喂食的场景,

public class CatAnimal {
    void eat(){
        System.out.println("猫吃鱼");
    }
}

public class DogAnimal {
    void eat(){
        System.out.println("狗啃骨头");
    }
}

public class Person {
    //人喂动物
    void feed(DogAnimal dog){
        dog.eat();
    }
    void feed(CatAnimal cat){
        cat.eat();
    }
}

public class negtive.AppTest {
	public static void main(String[] args) {
        Person person = new Person();
        person.feed(new DogAnimal());
        person.feed(new CatAnimal());
    }
}

看下类图,
在这里插入图片描述
Person 是直接依赖 Dog 和 Cat 的,若还要喂食其他动物,还需要在 Person 类中去重载 feed 方法。这是妥妥的反例。

// 定义一个动物接口: Animal
public interface Animal {
   public void eat();
}

public class CatAnimal implements Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

public class DogAnimal implements Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头");
    }
}

public class Person {
    void fead(Animal animal){
        animal.eat();
    }
}

在这里插入图片描述

Person 类不再直接依赖 Dog 和 Cat 了,而是依赖于 Animal 这个接口,当我们还需要去喂食其他动物时,只需要创建个具体动物类去实现动物接口并重写方法就 OK,不用再去重载 Person 的 fead 方法了,实现了解耦(稳定和变化的隔离)。

原则 2:开放封闭原则(OCP)—— 对扩展开放,对更改封闭

对扩展开放,对更改封闭: 类模块应该是可扩展的,但是不可修改

比如说当需求从画直线变更到画圆圈,那么不应该想着修改 Line,而是应该增加一个 Circle 类,让它继承自 Shape 类。

在这里插入图片描述

原则 3:单一职责原则(SRP)—— 一个类只做一件事

一个类应该只有一个引起它变化的原因,变化的方向隐含着类的责任。

原则 4:Liskov 替换原则(LSP)—— 子类必须能够替换它们的基类

子类必须能够替换它们的基类,即 所有使用父类对象的地方,都应该可以透明的替换为子类的对象

当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有是一个(is-a)关系。

原则 5:接口隔离原则(ISP)—— 接口应该小而完备

不应该强迫客户程序依赖它们不用的方法。

使用 多个专门的接口,而不是总接口。

原则 6:组合优于继承原则 —— 多使用关联,少使用继承

类继承通常为 “白箱复用”,对象组合通常为 “黑箱复用”。

  • 继承在某种程度上破坏了封装性,子类父类耦合度高。
  • 而对象组合只要求被组合的对象具有良好定义的接口,耦合度低。

多使用关联,少使用甚至不使用继承来达到复用已有对象的目的。

原则 7:封装变化点

使用封装来创建对象之间的分界层,让设计者可以在分界层一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的耦合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一杯水果茶!

谢谢你的水果茶啦~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值