设计模式——设计模式简介和七大原则

news2024/11/26 14:48:29

 导航: 

【黑马Java笔记+踩坑汇总】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud+黑马旅游+谷粒商城+学成在线+设计模式+牛客面试题

目录

一、通过经典面试题掌握重点

二、设计模式的目的和核心原则

三、设计模式七大原则

3.1 单一职责原则(Single Responsibility Principle)

3.2 接口隔离原则(Interface Segregation Principle)

3.3 依赖倒转原则(Dependence Inversion Principle)

3.3.1 介绍

3.3.2 依赖关系传递的三种方式

3.4 里氏替换原则(Liskov Substitution Principle)

3.5 开闭原则(Open Closed Principle)

3.6 迪米特法则(Demeter Principle)

3.7 合成复用原则(Composite Reuse Principle)

四、UML类图:统一建模语言

4.1、类图——依赖(dependence)

4.2、类图——泛化(Generalization)

4.3、类图——实现(Implementation)

4.4、类图——关联(Association)

4.5、类图——聚合(Aggregation)

4.6、类图——组合(Composition)


一、通过经典面试题掌握重点

原型设计模式 

  • 1)有请使用 UML 类图画出原型模式核心角色
  • 2)原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的源码(重写 clone 方法实现深拷贝、使用序列化来实现深拷贝)
  • 3)在 Spring 框架中哪里使用到原型模式,并对源码进行分析
<bean id="id01"class="com.atguigu.spring.bean.Monster" scope="prototype"/>
  • 4)Spring 中原型 bean 的创建,就是原型模式的应用
  • 5)代码分析 + Debug 源码

设计模式七大原则

  • 1)单一职责原则
  • 2)接口隔离原则
  • 3)依赖倒转原则
  • 4)里氏替换原则
  • 5)开闭原则(OCP)
  • 6)迪米特法则
  • 7)合成复用原则

要求:

  • 1)七大设计原则核心思想
  • 2)能够以类图的说明设计原则
  • 3)在项目实际开发中,你在哪里使用到了 OCP 原则

状态模式

金融借贷平台项目:借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式,请你使用状态模式进行设计,并完成实际代码

问题分析:这类代码难以应对变化,在添加一种状态时,我们需要手动添加 if/else ,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的BUG,难以维护

解释器设计模式

  • 1)介绍解释器设计模式是什么?
  • 2)画出解释器设计模式的 UML 类图,分析设计模式中的各个角色是什么?
  • 3)请说明 Spring 的框架中,哪里使用到了解释器设计模式,并做源码级别的分析

单例设计模式

单例设计模式一共有几种实现方式?请分别用代码实现,并说明各个实现方式的优点和实点?

  • 1)饿汉式 两种
  • 2)懒汉式 三种
  • 3)双重检查
  • 4)静态内部类
  • 5)枚举

二、设计模式的目的和核心原则

重要性: 

  1. 设计模式是软件设计中普遍存在问题的解决方案。
  2. 便于项目开发完成后维护项目(可读性、规范性)、新增功能。
  3. 你在实际项目中使用过什么设计模式,怎么使用的?解决了什么问题?
  4. 项目的功能模块和框架里会使用设计模式。

设计模式目的:

编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战。

设计模式是为了让程序(软件),具有更好的

  • 1)可复用性(即:相同功能的代码,不用多次编写,也叫做代码重用性)
  • 2)可读性(即:编程规范性,便于其他程序员的阅读和理解)
  • 3)可扩展性(即:当需要增加新的功能时,非常的方便,也叫做可维护性)
  • 4)可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  • 5)使程序呈现高内聚,低耦合的特性

设计原则核心思想

  • 1)找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 2)针对接口编程,而不是针对实现编程
  • 3)为了交互对象之间的松耦合设计而努力

设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”

三、设计模式七大原则

常用的七大原则有:

  • 1)单一职责原则
  • 2)接口隔离原则
  • 3)依赖倒转原则
  • 4)里氏替换原则
  • 5)开闭原则
  • 6)迪米特法则
  • 7)合成复用原则

3.1 单一职责原则(Single Responsibility Principle)

对类来说的,即一个类应该只负责一项职责

例如user表只负责存储用户相关的信息。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

注意事项和细节

  • 1)降低类的复杂度,一个类只负责一项职责
  • 2)提高类的可读性,可维护性
  • 3)降低变更引起的风险
  • 4)通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少可以在方法级别保持单一职责原则

案例:

方案1(违反单一原则),方法内条件判断,区分情景:

public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车");
        vehicle.run("轮船");
        vehicle.run("飞机");
    }
}

/**
 * 方式1的分析
 * 1.在方式1的run方法中,违反了单一职责原则
 * 2.解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
 */
class Vehicle{
    public void run(String type){
        if ("汽车".equals(type)) {
            System.out.println(type + "在公路上运行...");
        } else if ("轮船".equals(type)) {
            System.out.println(type + "在水面上运行...");
        } else if ("飞机".equals(type)) {
            System.out.println(type + "在天空上运行...");
        }
    }
}

方案2(单一职责):不同类,区分情景:

public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("汽车");
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");
    }
}

/**
 * 方案2的分析
 * 1.遵守单一职责原则
 * 2.但是这样做的改动很大,即将类分解,同时修改客户端
 * 3.改进:直接修改Vehicle类,改动的代码会比较少=>方案3
 */
class RoadVehicle{
    public void run(String type){
        System.out.println(type + "在公路上运行...");
    }
}
class WaterVehicle{
    public void run(String type){
        System.out.println(type + "在水面上运行...");
    }
}
class AirVehicle{
    public void run(String type){
        System.out.println(type + "在天空上运行...");
    }
}

方案3(方法级别单一职责):不同方法,区分情景:

public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle2 vehicle = new Vehicle2();
        vehicle.run("汽车");
        vehicle.runWater("轮船");
        vehicle.runAir("飞机");
    }
}

/**
 * 方式3的分析
 * 1.这种修改方法没有对原来的类做大的修改,只是增加方法
 * 2.这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
 */
class Vehicle2{
    public void run(String type){
        System.out.println(type + "在公路上运行...");
    }
    public void runWater(String type){
        System.out.println(type + "在水面上运行...");
    }
    public void runAir(String type){
        System.out.println(type + "在天空上运行...");
    }
}

3.2 接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

接口隔离原则(Interface Segregation Principle,ISP)是SOLID中的一个设计原则,它定义为“客户端应该不被迫依赖于它不使用的方法”,即一个类不应该强制依赖它不需要的接口。

接口隔离原则的主要目标是将庞大而臃肿的接口拆分成更小、更具体的接口,以方便客户端根据需求选择其所需的特定接口。这样可以大幅度减少客户端对于不必要的接口的依赖,使系统更加灵活、可维护和易于扩展。

一个典型的例子是,当我们需要使用一个接口时,通常是需要实现该接口的所有方法,但是实际上可能只需要用到部分方法。如果这个接口包含很多方法,就会造成实现类的代码冗余和依赖性过强。

通过合理的接口拆分和组合,可以使得接口更加精简,提高代码的复用性和可拓展性。同时,也有利于提高代码的可维护性,降低代码修改时的风险和维护成本。

需要注意的是,接口是一种描述行为的抽象,而隔离的目的是为了让接口更好地描述抽象行为,而不是让接口的数量变得多而复杂。因此,我们需要在接口隔离时保持适度,并根据具体情况进行选择和拆分。

不符合接口隔离原则的案例:

  • 类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,如果接口 Interface1 对于类 A 和类 C 来说不是最小接口(类只会用到接口里的部分方法,另一部分方法不需要还得实现),那么类 B 和类 D 必须去实现他们不需要的方法
  • 按隔离原则应当这样处理:将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

违法隔离的代码: 

interface Interface1 {
    void operation1();

    void operation2();

    void operation3();

    void operation4();

    void operation5();
}

class B implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3");
    }

    @Override
    public void operation4() {
        System.out.println("B 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("B 实现了 operation5");
    }
}

class D implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("D 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("D 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("D 实现了 operation3");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 operation5");
    }
}

/**
 * A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法
 */
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend2(Interface1 i) {
        i.operation2();
    }

    public void depend3(Interface1 i) {
        i.operation3();
    }
}

/**
 * C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
 */
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface1 i) {
        i.operation4();
    }

    public void depend5(Interface1 i) {
        i.operation5();
    }
}

拆分接口后的代码:

interface Interface1 {
    void operation1();
}

interface Interface2 {
    void operation2();

    void operation3();
}

interface Interface3 {
    void operation4();

    void operation5();
}

class B implements Interface1, Interface2 {

    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3");
    }
}

class D implements Interface1, Interface3 {

    @Override
    public void operation1() {
        System.out.println("D 实现了 operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 operation5");
    }
}

/**
 * A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法
 */
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend2(Interface2 i) {
        i.operation2();
    }

    public void depend3(Interface2 i) {
        i.operation3();
    }
}

/**
 * C类通过接口Interface1,Interface3依赖(使用)D类,但是只会用到1,4,5方法
 */
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface3 i) {
        i.operation4();
    }

    public void depend5(Interface3 i) {
        i.operation5();
    }
}

3.3 依赖倒转原则(Dependence Inversion Principle)

3.3.1 介绍

  • 1)高层模块不应该依赖低层模块,二者都应该依赖其抽象(接口或抽象类)
  • 2)抽象不应该依赖细节,细节应该依赖抽象
  • 3)依赖倒转(倒置)的中心思想是面向接口编程
  • 4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
  • 5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
  • 多态是实现依赖倒转原则的方法之一。

案例:用户类接收邮件、微信等信息。

违反依赖倒转:引用具体而非抽象:

/**
 * 方式1分析
 * 1.简单,比较容易想到
 * 2.如果我们获取的对象是微信,短信等等,则新增类,同时 Peron也要增加相应的接收方法
 * 3.解决思路:
 * 引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖
 * 因为Email,Weixin等等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符号依赖倒转原则
 */
class Email {
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Person {
    public void receive(Email email) {
        System.out.println(email.getInfo());
    }
}

改进:多态的方式引用抽象:

interface IReceiver {
    String getInfo();
}

class Email implements IReceiver {
    @Override
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Weixin implements IReceiver {
    @Override
    public String getInfo() {
        return "微信消息:Hello World!";
    }
}

class ShortMessage implements IReceiver {
    @Override
    public String getInfo() {
        return "短信信息:Hello World!";
    }
}

class Person {
    public void receive(IReceiver receiver) {
        System.out.println(receiver.getInfo());
    }
}

3.3.2 依赖关系传递的三种方式

  • 1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
  • 2)变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
  • 3)继承时遵循里氏替换原则

开关电视的案例: 

1)接口传递

 ITV接口是IOpenAndClose接口的普通方法参数

//方式1:通过接口传递实现依赖
//开关的接口
interface IOpenAndClose {
    void open(ITV tv);//抽象方法,接收接口
}
//ITV接口
interface ITV {
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    public void open(ITV tv){
        tv.play();
    }
}

2)构造方法传递

 ITV接口是OpenAndClose类的成员变量和构造方法参数 

//方式2:通过构造函数实现依赖
//开关的接口
interface IOpenAndClose {
    void open();//抽象方法
}
//ITV接口
interface ITV {
    public void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    private ITV tv; // 成员
    
    public OpenAndClose(ITV tv){ // 构造器
        this.tv = tv;
    }
    
    public void open(){
        this.tv.play();
    }
}

3)setter 方式传递

 ITV接口是OpenAndClose类的成员变量和setter方法参数  

//方式3,通过setter方法传递
interface IOpenAndClose{
    void open();//抽象方法
    void setTv(ITV tv);
}
//ITV接口
interface ITV{
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose{
    private ITV tv;
    public void setTv(ITV tv){
        this.tv=tv;
    }
    public void open(){
        this.tv.play();
    }
}

3.4 里氏替换原则(Liskov Substitution Principle)

面对对象OO 中继承性的思考和说明

  • 1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
  • 2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
  • 3)问题提出:在编程中,如何正确使用继承?=>里氏替换原则

基本介绍

  • 1)里氏替换原则在1988年,由麻省理工学院的以为姓里的女士提出的
  • 2)父类型对象替换成子类型对象后功能未变:如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象
  • 3)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  • 4)里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题

案例:

传统方案:子类把父类的减方法重写为加方法:整个继承体系的复用性会比较差。

public void test() {
    A a = new A();
    System.out.println("11-3=" + a.func1(11, 3));
    System.out.println("1-8=" + a.func1(1, 8));
    System.out.println("---------------------");

    B b = new B();
    System.out.println("11-3=" + b.func1(11, 3));
    System.out.println("1-8=" + b.func1(1, 8));
    System.out.println("11+3+9=" + b.func2(11, 3));
}

class A {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends A {
    @Override
    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }
}

改进方案:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替

//创建一个更加基础的基类
class Base {
    //将更基础的成员和方法写到Base类中
}

class A extends Base {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends Base {
    //如果B需要使用A类的方法,使用组合关系
    private A a;

    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }

    public int func3(int num1, int num2) {
        return this.a.func1(num1, num2);
    }
}

3.5 开闭原则(Open Closed Principle)

开:对扩展开放。

闭: 对修改关闭。

  • 1)开闭原则是编程中最基础、最重要的设计原则
  • 2)一个软件实体如类、模块和函数应该对扩展开放(对提供者而言),对修改关闭(对使用者而言)。用抽象构建框架,用实现扩展细节。增加了新功能后,原来使用的代码并没有做更改。
  • 3)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 4)编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则

案例:

方案一:传统方案

一个画图形的功能,类图设计如下:

class GraphicEditor {
    public void drawShape(Shape s) {
        if (s.m_type == 1) {
            drawRectangle(s);
        } else if (s.m_type == 2) {
            drawCircle(s);

        } else if (s.m_type == 3) {
            drawTriangle(s);
        }
    }

    public void drawRectangle(Shape r) {
        System.out.println("矩形");
    }

    public void drawCircle(Shape r) {
        System.out.println("圆形");
    }

    public void drawTriangle(Shape r) {
        System.out.println("三角形");
    }
}

class Shape {
    public int m_type;
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }
}

方式 1 的优缺点

  • 1)优点是比较好理解,简单易操作
  • 2)缺点是违反了设计模式的 OCP 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时喉,尽量不修改代码,或者尽可能少修改代码
  • 3)比如我们这时要新增加一个图形种类,我们需要做如下修改,修改的地方较多4)代码演示

方式 1 的改进的思路分析

把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现即可

这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可

使用方的代码就不需要修改,满足了开闭原则

方式 2 开闭原则:画图功能设为基类的抽象方法,新增实现类只需要继承基类并实现画图的抽象方法即可。

class GraphicEditor {
    public void drawShape(Shape s) {
        s.draw();
    }
}

abstract class Shape {
    int m_type;

    public abstract void draw();
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }

    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }

    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }

    @Override
    public void draw() {
        System.out.println("三角形");
    }
}

3.6 迪米特法则(Demeter Principle)

基本介绍

  • 1)一个对象应该对其他对象保持最少的了解
  • 2)类与类关系越密切,耦合度越大
  • 3)迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
  • 4)迪米特法则还有个更简单的定义:只与直接的朋友通信
  • 5)直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多:依赖、关联、组合、聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

注意事项和细节

  • 主要A类里存在方法里B类是直接朋友,那么A类所有方法局部变量出现的B类都是直接朋友。
  • 1)迪米特法则的核心是降低类之间的耦合
  • 2)但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

案例

传统方案:

1)有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id

//总部员工
class Employee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//学院员工
class CollegeEmployee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//学院员工管理 类
class CollegeManager {
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<>();    //是直接朋友
        CollegeEmployee collegeEmployee;    //是直接朋友
        for (int i = 0; i < 10; i++) {
            collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("学院员工id=" + i);
            list.add(collegeEmployee);
        }
        return list;
    }
}

//总部员工管理类
class SchoolManager {
    public List<Employee> getAllEmployee() {
//仅出现成员变量,方法参数,方法返回值中的类为直接的朋友
        List<Employee> list = new ArrayList<>();    //Employee 是直接朋友,出现在返回值
        Employee employee;    //是直接朋友
        for (int i = 0; i < 5; i++) {
            employee = new Employee();
            employee.setId("总部员工id=" + i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(CollegeManager sub) {
        System.out.println("--------------学院员工--------------");
        List<CollegeEmployee> list1 = sub.getAllEmployee();    //不是直接朋友,出现在局部变量
        for (CollegeEmployee collegeEmployee : list1) {
            System.out.println(collegeEmployee.getId());
        }
        System.out.println("---------------总部员工-------------");
        List<Employee> list2 = this.getAllEmployee();
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}

应用实例改进

  • 1)前面设计的问题在于 SchoolManager 中,CollegeEmployee 类并不是 SchoolManager 类的直接朋友(分析)
  • 2)按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
  • 3)对代码按照迪米特法则进行改进,将局部对象变量封装进参数里。
//学院员工管理类
class CollegeManager {
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<>();    //是直接朋友
        CollegeEmployee collegeEmployee;    //是直接朋友
        for (int i = 0; i < 10; i++) {
            collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("学院员工id=" + i);
            list.add(collegeEmployee);
        }
        return list;
    }
//改进,新增方法,输出学院员工信息
    public void printEmployee(){
        System.out.println("--------------学院员工--------------");
 //CollegeEmployee是直接朋友,出现在上面getAllEmployee()方法的返回值
        List<CollegeEmployee> list1 = getAllEmployee();   
        for (CollegeEmployee collegeEmployee : list1) {
            System.out.println(collegeEmployee.getId());
        }        
    }    
}
//总部员工管理类
class SchoolManager {
    public List<Employee> getAllEmployee() {
//仅出现成员变量,方法参数,方法返回值中的类为直接的朋友
        List<Employee> list = new ArrayList<>();    //Employee 是直接朋友,出现在返回值
        Employee employee;    //是直接朋友
        for (int i = 0; i < 5; i++) {
            employee = new Employee();
            employee.setId("总部员工id=" + i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(CollegeManager sub) {
        sub.printEmployee();    //改进,降低耦合,不用非直接朋友。
        System.out.println("---------------总部员工-------------");
        List<Employee> list2 = this.getAllEmployee();
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}

3.7 合成复用原则(Composite Reuse Principle)

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承

也就是把需要用到的类作为本类的参数、成员变量、局部变量。

  • 依赖(Dependency)指的是一个对象使用另一个对象的情况。通常是在一个对象的方法中传入另一个对象作为参数,或者在方法中创建另一个对象的实例。依赖关系是一种“短暂”的引用关系,一旦不再需要依赖对象就可以释放掉。

  • 合成(Composition):指的是两个或多个对象之间一种包含与被包含的关系。其中包含对象是整体,被包含对象是零部件,它们的生命周期是一致的,无法单独存在。例如,一辆汽车是由发动机、车轮、底盘等组成的,这些组成部分与它们组合成的整体汽车具有相同的生命周期。当整体消亡时,所有零部件也随之消亡。

  • 聚合(Aggregation):指的是两个或多个对象之间一种包含与被包含的关系,被包含对象可以存在于多个包含对象之间。在聚合关系中,被包含对象可以独立于包含对象存在,生命周期也不一定相同。例如,大学是由系部、学院、图书馆等组成的,这些部分可以独立存在,而且它们也可以属于不同的大学。即使整个大学消亡,它们仍然可以存在。

总之,依赖、合成和聚合是面向对象编程中描述对象关系的重要概念,它们有助于设计和实现具有良好扩展性和可维护性的应用程序。

案例

B类想用A类方法,如果直接继承,那么耦合性会提高,之后A类修改后B类也得跟着修改。

解决办法:

  • 把A类作为B类普通方法的形参;
  • 把A类作为B类成员变量,用setter方法
  • B类的普通方法里创建A类的对象;

四、UML类图:统一建模语言

  • 1)UML—-Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果
  • 2)UML 本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等
  • 3)使用 UML 来建模,常用的工具有 Rational Rose,也可以使用一些插件来建模

UML 类图

  • 1)用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系
  • 2)类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合

 

4.1、类图——依赖(dependence)

只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编译都通过不了

  • 1)类中用到了对方
  • 2)类的成员属性
  • 3)方法的返回类型
  • 4)方法接收的参数类型
  • 5)方法中使用到 

A用到B,那么A依赖B,即A------->B:

 

4.2、类图——泛化(Generalization)

泛化关系实际上就是继承关系,它是依赖关系的特例

A继承B,那么A➞B

4.3、类图——实现(Implementation)

实现关系实际上就是 A 类实现 B 类,它是依赖关系的特例 

A 实现B,则A➟ B

 

4.4、类图——关联(Association)

关联关系实际上就是类与类之间的联系,它是依赖关系的特例

  • 关联具有导航性:即双向关系或单向关系
  • 关系具有多重性:如“1”(表示有且仅有一个),“0...”(表示0个或者多个),“0,1”(表示0个或者一个),“n...m”(表示n到m个都可以),“m...*”(表示至少m个)

单向一对一关系

public class Person {
	private IDCard card;
}
public class IDCard {}

UML 类图

双向一对一关系

public class Person {
	private IDCard card;
}
public class IDCard {
	private Person person;
}

UML 类图

4.5、类图——聚合(Aggregation)

聚合关系表示的是整体和部分的关系整体与部分可以分开。聚合关系是关联关系的特例,所以它具有关联的导航性与多重性

B是A的未实例化成员变量,则B——◇A

如:一台电脑由键盘(keyboard)、显示器(monitor),鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来的,使用带空心菱形的实线来表示:

public class Mouse {}
public class Monitor {}
public class Computer {
    private Mouse mouse;
    private Monitor monitor;

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }

    public void setMonitor(Monitor monitor) {
        this.monitor = monitor;
    }
}

UML 类图

4.6、类图——组合(Composition)

组合关系也是整体与部分的关系,但是整体与部分不可以分开

B是A的实例化成员变量,则B<——◇A

如果我们认为 Mouse、Monitor 和 Computer 是不可分离的,则升级为组合关系

public class Mouse {}
public class Monitor {}
public class Computer {
    private Mouse mouse = new Mouse();
    private Monitor monitor = new Monitor();
}

UML 类图

再看一个案例,在程序中我们定义实体:Person 与 IDCard、Head,那么 Head 和 Person 就是组合,IDCard 和 Person 就是聚合

public class IDCard{}
public class Head{}
public class Person{
    private IDCard card;
    private Head head = new Head();
}

UML 类图

但是如果在程序中,Person 实体中定义了对 IDCard 进行级联删除,即删除 Person 时连同 IDCard 一起删除,那么 IDCard 和 Person 就是组合了

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 安装 NFS 实现文件目录共享

一、背景介绍 项目中&#xff0c;之前是单节点部署服务&#xff0c;关于文件的上传和下载&#xff0c;只要配置好路径即可。 而当集群化部署后&#xff0c;就会有问题。比如&#xff1a;文件上传的时候&#xff0c;访问的是主机1&#xff0c;而想要现在文件的时候&#xff0c…

【MST】ABC235 E - MST + 1

一开始想的是分类讨论&#xff0c;看那条边加了之后成不成环&#xff0c;如果不成环且权值在前n-1&#xff0c;则一定在MST里&#xff0c;不在前n-1则不在MST里&#xff1b;如果成环了&#xff0c;如果权值不在前n-1&#xff0c;则不在MST里&#xff0c;如果权值在前n-1&#x…

spark 数据的加载和保存(Parquet、JSON、CSV、MySql)

spark数据的加载和保存 SparkSQL 默认读取和保存的文件格式为 parquet 1.加载数据 spark.read.load 是加载数据的通用方法 scala> spark.read. csv format jdbc json load option options orc parquet schema table text textFile 如果读取不同格式的数据&#xff0c;可以…

docker安装fastdfs

1 拉取镜像 docker pull morunchang/fastdfs如果网速下载慢&#xff0c;可以参考资料文件夹中给大家导出的镜像包上传到 Linux服务器上&#xff0c;通过docker load -i my_fdfs.tar 加载镜像。 使用 docker images查看是否成功 2 运行tracker docker run -d --name tracker -…

MySQL高阶——索引设计的推演

前言 MySQL在我们工作中都会用到&#xff0c;那么我们最常接触的就是增删改查&#xff0c;而对于增删改查来说&#xff0c;我们更多的是查询。但是面试中&#xff0c;面试官又不会问你什么查询是怎么写的&#xff0c;都是问一些索引啊&#xff0c;事务啊&#xff0c; 底层结构…

C. Multiplicity(DP + 分解因数)

Problem - C - Codeforces 给定一个整数数组a1&#xff0c;a2&#xff0c;...&#xff0c;an。 如果可以从a中删除一些元素得到b&#xff0c;则称数组b为a的子序列。 当且仅当对于每个i&#xff08;1≤i≤k&#xff09;&#xff0c;bi是i的倍数时&#xff0c;数组b1&#xff…

Spring Data JPA 快速上手

一、JPA的概述 JPA的全称是Java Persisitence API&#xff0c;即JAVA持久化API&#xff0c;是sum公司退出的一套基于ORM的规范&#xff0c;内部是由一些列的接口和抽象类构成。JPA通过JDK5.0注解描述对象-关系表的映射关系&#xff0c;并将运行期的实体对象持久化到数据库中。…

Mysql 分库分表 Mycat

0 课程视频 https://www.bilibili.com/video/BV1Kr4y1i7ru?p163&spm_id_frompageDriver&vd_sourceff8b7f852278821525f11666b36f180a 1 单库问题 1.1 热点数据多 -> 缓冲区不足 ->内存不足 1.2 数据多 -> 磁盘不足 1.3 请求数据量多 -> 带宽不足 1…

C/C++的命名空间和调用函数的详细讲解

目录 空函数 调用函数 调用 执行流程 命名空间 在创建函数时&#xff0c;必须编写其定义。所有函数定义包括以下组成部分&#xff1a; 名称&#xff1a;每个函数都必须有一个名称。通常&#xff0c;适用于变量名称的规则同样也适用于函数名称。形参列表&#xff1a;调用函…

算法记录lday4 LinkedList链表交换 删除倒数N个点 环形链表

今日任务 ● 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II 两两交换链表中的节点 题目描述 Given a linked list, swap every two adjacent nodes and return its head. You must solve the problem without modi…

一天吃透Redis面试八股文

Redis连环40问&#xff0c;绝对够全&#xff01; Redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09;是一个使用 C 语言编写的&#xff0c;高性能非关系型的键值对数据库。与传统数据库不同的是&#xff0c;Redis 的数据是存在内存中的&#xf…

Golang每日一练(leetDay0052)

153. 寻找旋转排序数组中的最小值 Find Minimum In Rotated Sorted Array 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; …

纸模图纸的发展历程+纸模图纸免费下载-手工985

纸模图纸是一种用于制作模型的图纸&#xff0c;通常用于制作卡通纸模图纸、飞机纸模图纸、船只纸模图纸、汽车纸模图纸、建筑纸模图纸等模型。 纸模图纸的历史 纸模图纸的历史可以追溯到19世纪末期。当时&#xff0c;一些欧洲的制图师开始使用纸板和纸张来制作建筑模型&#xf…

【MATLAB数据处理实用案例详解(21)】——利用SOM自组织映射网络实现癌症样本数据分类和利用SOM自组织映射网络实现柴油机故障分类详解

目录 一、SOM原理分析二、SOM拓扑结构分析三、三种SOM拓扑结构3.1 Gridtop()网格拓扑结构3.2 Hextop()六角形拓扑结构3.3 Randtop()随机拓扑结构 四、利用SOM自组织映射网络实现癌症样本数据分类4.1 问题描述4.2 输入数据4.3 网络建立和训练4.4 验证4.5 预测4.6 运行结果 五、利…

wait/waitpid(重点)介绍

谢谢你的阅读&#xff0c;如有错误请大佬留言 目录 引子&#xff1a; waitpid 返回值介绍 参数介绍 pid status options: 引子&#xff1a; 当一个进程创建子进程后&#xff0c;如果子进程工作结束后会进入僵尸状态&#xff0c;等待父进程回收子进程资源&#xff08;退…

烟花智能直播助手,直播带货必备爆单工具【直播助手脚本+技术教程】

烟花智能直播助手软件教程介绍&#xff1a; 1.账号管理:可以登陆多个账号,一键切换 2.商品批量管理&#xff1a;可一键删除/添加直播商品,一键设置商品卖点 3.自动弹讲解:可设置指定,单个,列表循环自动弹商品讲解 4.智能文字客服:可设置指定关键词对公屏信息进行回复,不限添加条…

【C语言】C语言总结

声明&#xff0c;本文来自中国mooc中的翁凯C语言总结 第一章介绍 变量定义 变量定义的一般形式为&#xff1a;<类型名称><变量名称>变量需要一个名字&#xff0c;变量的名字是一种表示符&#xff0c;意思是用来识别不同的标识符标识符的基本构造规则为&#xff…

近世代数 笔记与题型连载 第十一章(正规子群与商群)

文章目录 基本概念1.正规子群2.商群 常见题型1.正规子群的判定和证明2.给定群和正规子群&#xff0c;求商群 基本概念 1.正规子群 正规子群的定义&#xff1a;设<G,※>是群&#xff0c;H是G的子群。如果对于G中的任意元素g&#xff0c;都有gHHg&#xff0c;则称H是G的正…

org.apache.poi 设置 Excel 单元格颜色 RGB

一、背景说明 在使用 org.apache.poi 导出 Excel 时&#xff0c;需要设置部分单元格的颜色。 可以使用方法&#xff1a;org.apache.poi.ss.usermodel.CellStyle.setFillForegroundColor() 和 org.apache.poi.ss.usermodel.CellStyle.setFillPattern() 来设置单元格的颜色和填…

代码随想录 LeetCode数组篇 二分查找

文章目录 &#xff08;简单&#xff09;35. 搜索插入位置&#xff08;*中等&#xff09;34. 在排序数组中查找元素的第一个和最后一个位置&#xff08;简单&#xff0c;常见面试题&#xff09;69. x的平方根&#xff08;简单&#xff09; 367. 有效的完全平方数 # &#xff08;…