降低耦合度的简单例子(初学者需要)

7
刚刚毕业,我遇到了一些需要减少耦合的代码。但我并不完全理解所有的概念,希望有一个简单的例子来帮助我。为了让你开始,我有一个人类,只有一个字段name。我在这个类中有一个方法来连接一些文本。
我知道这是一个愚蠢的例子,大多数人永远不会考虑在这种简单的情况下减少耦合,但我只想要一个简单的例子来帮助我完全理解代码和概念。
在主窗口的后面代码中,我放了一个文本框和一个按钮。当窗口加载时,它显示人物x名称字段的当前值。当点击按钮时,调用x.PersonAddText方法。目前,此示例的耦合计算为8。按钮单击事件为3,窗口加载事件为3。
使用此示例,我们是否可以将其降低到少于这两个事件之一或两者都少于此?
以下是我的所有代码:
我的人类:
public class Person
{
    //Fields
    private string name;

    //Properties
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    //Constructors
    public Person()
    {
        name = "joe";
    }

    //Methods
    public string PersonAddText(string text)
    {
        return name += " - " + text;
    }

    //Interfaces (or additional code below here please to aid understanding)
}

我的代码后端:

    Person x = new Person();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        txtname.Text = x.Name;
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        txtname.Text = x.PersonAddText(txtname.Text);
        txtname.Text = x.Name;
    }

我的简单XAML:

<Grid>
    <TextBox Name="txtname" Margin="12,12,12,0" Height="23" VerticalAlignment="Top" />
    <Button Content="Add Text" Margin="12,41,12,0" Name="button1" VerticalAlignment="Top" Click="button1_Click" />
</Grid>

我很难理解互联网上关于这方面的教程。据我所见,有三种方法可以做到这一点(如果可能的话,最好将我的代码转换为这三个示例之一):
  • 服务定位器
  • 依赖注入
  • 控制反转(IoC)
文章解释得非常好,但是对我来说,示例与我无关,因为他使用的是VB和ASP.Net以及数据库连接字符串。这与我需要的完全相反,我不想在学习概念、思考如何将其应用于相关内容时还要考虑如何翻译代码。虽然示例很好,但它太多了,我真的会非常感激任何额外的帮助。
编辑历史记录:更正了拼写。增加以下内容以澄清我的问题:
我理解耦合和内聚背后的理论以及为什么应该减少一个并增加另一个。但我们在大学里从未编写过任何示例。此外,虽然没有在大学里涉及,但我确实了解接口。然而,我不知道如何使用它们来减少耦合。

我在上面提到的文章中添加了一个链接。

编辑2:目前我得到的是以下内容:

public interface IPerson 
{ 
    string Name { get; set; } 
    string PersonAddText(string text); 
} 

public class Person : IPerson 
{
    //The code from the person class above
}

我现在该如何在mainwindow代码后台使用它?我猜我应该替换。
Person x = new Person();

使用

IPerson x = new Person(); 

这是否正确,如果是,我还需要做些什么。我问的原因是因为我在Visual Studio报告的代码耦合图中仍然看不到任何减少(事实上,在主窗口代码后面它增加了1)。


你到底不明白什么?你对哪个概念有困难? - Oded
我知道耦合和内聚在理论上的意义,但我不知道如何编写代码。特别是我们在大学里从未涉及接口(是的,我知道,很棒的大学)。我也理解接口,但我不知道如何使用它们来减少耦合。 - Francis Rodgers
3个回答

3

编辑

很高兴我的回答有所帮助,让我稍微更新一下。如果要将您的问题作为直接答案使用,则需要更改的仅是您的字段声明:

Person x = new Person();

为了

IPerson x = new Person();

您的代码现在知道接口中指定的属性和方法,并且耦合度更低,因为您以后可以将 new Person() 替换为 new Student()。只要对象实现了该接口。您的代码现在应该可以正常工作,而无需进行任何必要的更改。 附注 我建议考虑延迟加载 x 人,并使用一个更容易识别的属性名称。注意:这不是回答您的问题,只是一个建议。 :)
private IPerson _CurrentPerson = null;
private IPerson CurrentPerson
{
    get
    {
        if (this._CurrentPerson == null)
        {
            this._CurrentPerson = new Person();
        }
        return this._CurrentPerson
    }
    set
    {
        this._CurrentPerson = value;
    }
}

解耦是指两个或多个代码块不应该相互依赖。控制反转是将对象的耦合绑定在运行时,从而允许更灵活、更少耦合的对象和它们的实例。控制反转最好与接口一起使用。接口定义了 ClassA 将执行MethodX并具有PropertyY。我们的主要对象不关心在运行时返回什么对象,只要它可以满足一个接口即可。
在您上面的示例中,您将希望对您的人员类进行接口处理,可能如下所示:
public interface IPerson
{
    string Name { get; set; }
    string PersonAddText(string text);
}

public class Person : IPerson
{
    // your code here
}

然后,在您的主方法调用中,不再明确使用Person对象,而是使用实现了接口IPerson的对象实例。连接接口和对象的“挂钩”可以通过各种不同的库来实现,这将有助于设置依赖项。在我的经验中,我使用过StructureMapMicrosoft's Enterprise Library。它们可能有点棘手,但一旦设置好了,您就可以像这样做...

public void MainMethod_InInterfaceLayer()
{
    // container is an instance of UnityContainer
    Person newPerson = container.Resolve<IPerson>();
}

我知道这不是一个完整的答案,但希望它能有所帮助。 :)

到目前为止,这个答案似乎是最容易理解的。我正在尝试它,并会回到你那里。我唯一遇到的问题是你提供的链接很快就深入了很多,而对于这个简单的例子来说,理解更重要。如果您可以在不使用扩展名的情况下编写所需的代码而不是MainMethod_InInterfaceLayer(),我将授予您答案。如果可能的话,请通过更改MainWindow代码后面的操作来实现。感谢迄今为止的帮助。 - Francis Rodgers
1
我已经更新了我的答案。希望与@Oded的答案结合起来,这就是你要找的东西。 - Richard
谢谢Richard。我现在正在实施你的答案,解释非常好。我会假设我的例子太简单了,无法将Visual Studios耦合标记从3降至2。正如我在编辑中所解释的那样,当我按照你所描述的方式操作时,没有减少发生。感谢您的帮助。我也会尝试你的副作用,但我觉得这与主题有些不相关(虽然我很感激你的建议)。如果您有时间,我想知道在这种情况下延迟加载的好处以及您提出建议的思路是什么。 - Francis Rodgers
只是作为附注,如果您不打算重新初始化“_CurrentPerson”字段,则不需要为“CurrentPerson”属性添加setter。此外,如果您喜欢语法糖,可以通过以下方式替换整个“CurrentPerson”属性:private IPerson CurrentPerson => _CurrentPerson ?? (_CurrentPerson = new Person()); - Gobe

2

假设你有一个IPerson接口和几个实现类(Person, Student, Teacher等),并且你有一些代码只需要对IPerson进行操作。

现在有:

IPerson x = new Person();

在你的代码后台,强烈地将它与Person类耦合。

控制反转通过让依赖项来自外部而不是在类内部创建来发挥作用。

通常使用依赖注入来实现这一点 - 将IPerson传递到类中而不是直接创建类。您可以通过将一个实例传递给构造函数(构造函数注入)或作为属性(属性注入)来实现这一点。

服务定位器是另一种获取依赖项而不硬编码它们的方法 - 该模式“定位”类型/接口的实例。

以下是如何向类注入依赖项的示例:

public class MyClass
{
  IPerson person;

  public MyClass(IPerson p)
  {
    person = p; // injected an instance of IPerson to MyClass
  }

  // can now use person in any method of the class, or pass it around:

  public void MyMethod()
  {
     string name = person.Name;
  }
}

首先,让我感谢您的回答。使用richards答案中的代码,我已经成功制作了一个接口。我现在遇到的问题是如何使用它来替换Code Behind中的代码。或者这就是您上面展示给我的内容。但对于我来说并不十分清晰,如果您能提供更多的代码进行澄清,我会授予您答案。再次感谢您。 - Francis Rodgers
1
@FrancisRodgers - 添加了一个示例,展示了类MyClass如何与IPerson的任何实现解耦,因此可以使用_任何_实现者。同时展示了构造函数注入的情况。 - Oded

0
假设你有一个人类(Person class),它可以从数据库中加载自己,进行更改,并在他或她死亡时发送电子邮件。
class Person 
{
    ...
    void LoadUsingId(int id);
    void HaveDiedWillMail();
    void SetFirstName(string name);
    ...
}

你可以看到这个人实际上正在做三件事情(至少!)。

它知道如何与数据库通信来加载自身。 它知道如何发送电子邮件。 它可以改变自己。

任何代码理论上都应该只负责一件事情。为了降低这些组件之间的耦合度,必须将它们分离。解耦它们,使它们能够独立工作。

class Person 
{
    ...
    void LoadUsingId(PersonRepository person);
    void HaveDiedWillMail(IMailer mailer);
    void SetFirstName(string name);
    ...
}

在这个人为的例子中,Person不知道如何从数据库中加载信息。忽略“加载”这个功能应该解耦的事实。发送邮件也已经被解耦,因为你告诉Person.HaveDiedWillMail你想使用特定的邮件服务(IMailer mailer)。
依赖注入、服务容器等技术会自动查找Person需要/想要的组件,以使其正常运行,这是解耦的一个额外层次,使将所有单独的部分连接起来更容易。

我不同意一个对象(模型)应该以松散耦合的方式从数据库中加载自己。一个模型应该只做一件事:代表一个真实的对象。从数据库中加载数据是另一个对象的工作,它也应该只做一件事:从数据库中加载数据。实际上,在模型和数据库之间加入两个层级会更好。 - Kendall Frey
我也同意你的看法。但是我并不打算在这里解释一切,甚至不是什么明智的东西...而是解耦。 - Jaapjan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接