GRASP 软件设计原则

内容纲要

实验目的:

熟练掌握GRASP软件设计原则,从代码细节层面,掌握基础的代码撰写规范。

实验要求:

设有2个类A和B,请给出以下5种创建者原则的代码实例:A类组合B类,A类聚合B类,A类记录B类,A类紧密的使用B类。(推荐c#、java 或Python)

设有2个类A和B,请给出3种不同的代码实例,使得A类和B类产生内容耦合,并阐述3个实例耦合的优缺点。(推荐c#、java 或Python)

设有2个类A和B,请给出3种不同的代码实例,使得A类和B类产生公共耦合,并阐述3个实例耦合的影响范围。(推荐c#、java 或Python)

设有2个类(接口、抽象类)A和B,请给出3种不同的代码实例,使得A类和B类产生对象耦合,并阐述3个实例耦合程度高低。(推荐c#、java 或Python)

设有2个类A和B,请给出2种不同的代码实例,使得A类和B类产生控制耦合,但是要求其中1种是正向的(好的)控制耦合,另一种是负向的(不好的)控制耦合。(推荐c#、java 或Python)

请设计一个界面,界面中只有三个按钮,分别是A,B和C。 请遵循控制器设计原则实现以下功能:

默认按下A输出“Hello World!”。 按下B后,再去按下A时,按输出“执行B功能!”。按下C后,再去按下A时,按输出“执行C功能!”。(建议C#-WINFORM,Java-Swing,或Python-tkinter)

实验内容:

设有2个类A和B,请给出以下5种创建者原则的代码实例:A类组合B类,A类聚合B类,A类记录B类,A类紧密的使用B类。

A类组合B类

A类组合B类意味着A类包含B类的实例,并对B类的实例具有完全的控制权。在这种情况下,A类是B类对象的创建者。

代码
class A {

    private B b;

    public A(B b) {
        this.b = b;
    }

    public void doSomething() {
        b.doSomething();
    }
}

class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

class Main {

    public static void main(String[] args) {
        A a = new A(new B());
        a.doSomething();
    }
}
结果

image-20231010195242147

A类聚合B类

A类聚合B类意味着A类包含B类的实例,但对B类的实例没有完全的控制权。在这种情况下,A类仍然是B类对象的创建者。

代码
class A {

    private B b;

    public A() {
        b = null;
    }

    public void setB(B b) {
        this.b = b;
    }

    public void doSomething() {
        b.doSomething();
    }
}

class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

public class Main {

    public static void main(String[] args) {
        A a = new A();
        a.setB(new B());
        a.doSomething();
    }
}
结果

image-20231010195319409

A类记录B类

A类记录B类意味着A类保留B类的实例引用,但对B类的实例没有任何控制权。在这种情况下,A类不是B类对象的创建者。

代码
package three;

import java.util.ArrayList;
import java.util.List;

class A {

    private final List<B> bs;

    public A() {
        bs = new ArrayList<>();
    }

    public void addB(B b) {
        bs.add(b);
    }

    public void doSomething() {
        for (B b : bs) {
            b.doSomething();
        }
    }
}

class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

public class Main {

    public static void main(String[] args) {
        A a = new A();
        a.addB(new B());
        a.addB(new B());
        a.doSomething();
    }
}
结果

image-20231010195348862

A类紧密的使用B类

A类紧密的使用B类意味着A类需要频繁使用B类的实例。在这种情况下,A类可以将B类的实例作为其属性来创建。

代码
class A {

    private B b;

    public A() {
        b = new B();
    }

    public void doSomething() {
        b.doSomething();
    }
}

class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

public class Main {

    public static void main(String[] args) {
        A a = new A();
        a.doSomething();
    }
}
结果

image-20231010195414654

设有2个类A和B,请给出3种不同的代码实例,使得A类和B类产生内容耦合,并阐述3个实例耦合的优缺点。

内容耦合

当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。

代码1
public class A {

    private B b;

    public A(B b) {
        this.b = b;
    }

    public void doSomething() {
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

上述实例中,A类包含一个B类的实例引用,并在 doSomething()方法中直接调用B类的实例方法。这种耦合方式的优点是简单易懂,缺点是A类和B类的耦合度很高,A类的变化会影响B类,B类的变化也会影响A类。

代码2
public class A {

    private B b;

    public A() {
        b = new B();
    }

    public void doSomething() {
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

上述实例中,A类在构造函数中创建了一个B类的实例,并将该实例引用保存在自己的属性中。A类对B类的实例引用有了更大的控制权。

代码3
public class A {

    private B b;

    public A() {
        b = new B();
    }

    public void doSomething() {
        b.doSomething(10);
    }
}

public class B {

    public void doSomething(int n) {
        System.out.println("B is doing something with n = " + n);
    }
}

上述实例中,A类在 doSomething()方法中调用B类的实例方法,并传递了一个参数。与实例2类似,但A类对B类的实例方法的调用有了更大的控制权。

优缺点

优点:

直接访问:内容耦合允许模块之间直接访问彼此的内部数据和实现细节,这可以使代码编写和理解更加直观和简单。

高效性:因为模块之间可以直接共享数据,所以内容耦合通常在性能上较为高效。没有额外的数据传递开销。

灵活性:有时候,直接访问内部数据可以提供更大的灵活性,允许某些优化和特定的功能。

缺点:

高耦合度:内容耦合是一种紧密耦合,它使模块之间高度依赖彼此的内部实现细节。这意味着对一个模块的修改可能需要修改其他模块。

可维护性差:因为模块之间紧密耦合,所以很难单独修改或维护一个模块,而不影响其他模块。

可测试性差:内容耦合使模块的单元测试变得困难,因为模块的内部实现细节通常对测试不可见。

可扩展性差:随着系统的增长和变化,内容耦合可能会导致代码的脆弱性,使得难以添加新功能或模块。

依赖性高:内容耦合使得模块之间的依赖性高,降低了代码的独立性。

设有2个类A和B,请给出3种不同的代码实例,使得A类和B类产生公共耦合,并阐述3个实例耦合的影响范围。

公共耦合

两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。

代码1
public class A {

    public static int count = 0;

    public void doSomething() {
        count++;
    }
}

public class B {

    public void doSomething() {
        System.out.println(count);
    }
}

上述实例中,A类和B类都访问了公共变量count。这样,A类和B类之间的耦合度就很高。如果count的值发生变化,A类和B类都需要修改。

代码2
public class A {

    public static List<String> list = new ArrayList<>();

    public void doSomething() {
        list.add("A");
    }
}

public class B {

    public void doSomething() {
        System.out.println(A.list);
    }
}

上述实例中,A类和B类都访问了公共列表list。这样,A类和B类之间的耦合度就很高。如果list的内容发生变化,A类和B类都需要修改。

代码3
public class A {

    public static Map<String, Integer> map = new HashMap<>();

    public void doSomething() {
        map.put("A", 1);
    }
}

public class B {

    public void doSomething() {
        System.out.println(A.map);
    }
}

上述实例中,A类和B类都访问了公共映射map。这样,A类和B类之间的耦合度就很高。如果map的内容发生变化,A类和B类都需要修改。

优缺点

优点:

简单直接:公共耦合通常很容易实现,因为多个模块可以直接访问共享的数据。

高效性:共享数据环境允许多个模块之间共享信息,这可以提高数据的传输和访问效率。

全局访问:共享数据通常具有全局可见性,因此多个模块可以轻松地访问相同的数据。

缺点:

高耦合度:公共耦合导致模块之间高度耦合,因为它们共享相同的数据。这意味着对公共数据的任何更改可能会影响多个模块,增加了系统的复杂性和维护难度。

竞态条件:由于多个模块同时访问共享数据,可能会导致竞态条件和数据不一致性问题。必须采取额外的措施来管理并发访问。

难以跟踪:在大型系统中,共享的数据可能被多个模块修改和访问,这使得难以跟踪哪个模块对数据进行了更改。

不适合大型系统:在大型软件系统中,公共耦合可能导致混乱和不可维护性,因此在这种情况下通常不建议使用。

设有2个类(接口、抽象类)A和B,请给出3种不同的代码实例,使得A类和B类产生对象耦合,并阐述3个实例耦合程度高低。

对象耦合

面向对象中的耦合指的就是不同对象之间相互关联的紧密程度。

代码1
public class A {

    private B b;

    public A() {
        b = new B();
    }

    public void doSomething() {
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}
public class A {

    private B b;

    public A() {
        b = new B();
    }

    public void doSomething() {
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

上述实例中,A类持有B类的实例。这样,A类和B类之间存在直接的依赖关系,A类的变化会影响B类,B类的变化也会影响A类。这种耦合程度是高耦合。

代码2
public class A {

    public void doSomething() {
        B b = new B();
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        System.out.println("B is doing something");
    }
}

上述实例中,A类在需要使用B类时,会创建一个新的B类实例。这种耦合程度是中耦合。

代码3
public interface B {

    void doSomething();
}

public class A {

    public void doSomething(B b) {
        b.doSomething();
    }
}

public class BImpl implements B {

    @Override
    public void doSomething() {
        System.out.println("B is doing something");
    }
}

上述实例中,A类通过接口或抽象类来引用B类。这种耦合程度是低耦合。

总结

实例1中,A类持有B类的实例。这样,A类和B类之间存在直接的依赖关系,A类的变化会影响B类,B类的变化也会影响A类。这种耦合程度是高耦合。

实例2中,A类在需要使用B类时,会创建一个新的B类实例。A类仍然会直接使用B类的实例。因此,这种耦合程度是中耦合。

实例3中,A类通过接口或抽象类来引用B类。这样,A类和B类之间只存在间接的依赖关系。如果A类需要使用B类的某个方法,则需要传递一个B类的实例给A类。这种耦合程度是低耦合。

设有2个类A和B,请给出2种不同的代码实例,使得A类和B类产生控制耦合,但是要求其中1种是正向的(好的)控制耦合,另一种是负向的(不好的)控制耦合。

控制耦合

一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。

代码1
public class A {

    public void doSomething() {
        B b = new B();
        b.doSomething();
    }
}

public class B {

    public void doSomething() {
        // todoList
    }
}

上述实例中,A类调用B类的doSomething()方法,控制B类的行为。这种耦合是正向的,因为A类可以控制B类的行为。这种耦合是好的,因为它可以将A类和B类的职责分离。

代码2
public class A {

    public void doSomething() {
        B b = new B();
        b.doSomething(true);
    }
}

public class B {

    public void doSomething(boolean flag) {
        // todoList,根据flag的值来决定做什么
    }
}

上述实例中,A类通过传递一个boolean值flag给B类的doSomething()方法,控制B类的行为。这种耦合是负向的,因为A类可以控制B类的行为,但这种控制是通过传递一个参数来实现的。这种耦合是不好的,因为它会增加A类的复杂性,降低A类的可测试性。

总结

控制耦合是指一个模块通过传递参数或控制变量来控制另一个模块的行为。控制耦合可以分为正向的和负向的。正向的控制耦合是好的,因为它可以将A类和B类的职责分离。负向的控制耦合是不好的,因为它会增加A类的复杂性,降低A类的可测试性。

请设计一个界面,界面中只有三个按钮,分别是A,B和C。请遵循控制器设计原则实现以下功能:

默认按下A输出“Hello World!”。 按下B后,再去按下A时,按输出“执行B功能!”。按下C后,再去按下A时,按输出“执行C功能!”。

代码

import tkinter as tk

class ButtonController:
    def __init__(self, root_a):
        self.previous_button = None
        self.root = root_a
        self.label = tk.Label(root_a, text="------------请点击------------")
        self.label.pack()

        self.button_a = tk.Button(root_a, text="A", command=self.on_button_a_click)
        self.button_a.pack()

        self.button_b = tk.Button(root_a, text="B", command=self.on_button_b_click)
        self.button_b.pack()

        self.button_c = tk.Button(root_a, text="C", command=self.on_button_c_click)
        self.button_c.pack()

    def on_button_a_click(self):
        if self.previous_button == "B":
            self.label.config(text="执行B功能!")
        elif self.previous_button == "C":
            self.label.config(text="执行C功能!")
        else:
            self.label.config(text="Hello World!")

        self.previous_button = "A"

    def on_button_b_click(self):
        self.previous_button = "B"

    def on_button_c_click(self):
        self.previous_button = "C"

root = tk.Tk()
button_controller = ButtonController(root)
root.mainloop()

测试

image-20231010205143618

按下按钮 A,显示“Hello World!”。

image-20231010205207980

按下按钮 B,再去按下按钮 A,显示“执行B功能!”。

image-20231010205229980

按下按钮 C,再去按下按钮 A,显示“执行C功能!”。

image-20231010205246518

控制器设计原则

将界面和业务逻辑分离:界面负责显示和处理用户输入,业务逻辑负责处理按钮的点击事件。

使用接口或抽象类来定义交互:控制器和界面之间使用接口或抽象类来定义交互,避免直接访问彼此的内部组件。

将业务逻辑封装在控制器中:控制器负责处理按钮的点击事件,并将业务逻辑封装在控制器中。

将控制器设计为单一职责:每个控制器只负责一个功能,即处理按钮的点击事件。

该程序使用了 ButtonController 类来实现控制器。ButtonController 类只负责处理按钮的点击事件,将业务逻辑封装在 on_button_a_click()、on_button_b_click() 和 on_button_c_click() 方法中。

在 on_button_a_click() 方法中,会判断按钮 A 之前被哪个按钮点击。如果是按钮 B 或按钮 C,则将 Label 组件的文本设置为相应的功能说明。如果是按钮 A,则将 Label 组件的文本设置为“Hello World!”。

因此,该程序遵循了控制器设计原则,提高了软件的可维护性、可测试性和可扩展性。

实验总结:

简单地说,软件工程中对象之间的耦合度就是对象之间的依赖性。指导使用和维护对象的主要问题是对象之间的多重依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。

有软硬件之间的耦合,还有软件各模块之间的耦合。耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接口。

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注