耦合。最佳实务


11

从这个线程开始,我开始

单例模式

这让我开始思考班级之间的耦合程度以及如何最好地实现松散耦合。请记住,我是一名新程序员(我的第一份工作开始已经四个月了),这确实是我对此的首要考虑,并且非常热衷于理解这一概念。

那么,究竟是松耦合还是重耦合呢?在当前(也是第一个项目)中,我正在研究ac#winforms项目,该项目的GUI部分创建对象和对其事件的订阅,当它们被触发时,GUI创建另一个对象(在本示例中为datagridview(一个类是我创建的,它包装了一个标准的datagridview并添加了其他功能)并将其附加到GUI。这是不好的结合还是好的?

我真的不想养成不良习惯并开始编写不好的代码,因此,我感谢您的答复。

Answers:


11

为了使您的代码松散耦合,这里要记住一些简单的事情:

第1部分:

技术上称为“关注分离”。每个类都有特定的角色,它应该处理业务逻辑或应用程序逻辑。尝试避免同时兼顾这两种责任的阶级。即,管理(广义)数据的类是应用程序逻辑,而使用数据的类是业务逻辑。

我个人将此称为(在我自己的小世界中)create it or use it。一个类应该创建一个对象或使用一个对象,但绝不能同时使用这两个对象。

第2部分:

如何实施关注分离。
作为起点,有两种简单的技术:

注意:设计模式不是绝对的。
它们应该根据情况进行定制,但是具有与所有应用程序相似的基本主题。因此,不要看下面的示例并说我必须严格遵循这个例子。这些仅是示例(在此方面作了些许改动)。

依赖注入

这是您传递类使用的对象的地方。您基于接口传入的对象,因此您的类知道如何处理该对象,而无需知道实际的实现。

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

在这里,我们将流注入令牌生成器中。令牌生成器不知道流是什么类型,只要它实现std :: istream的接口即可。

服务定位器模式

服务定位器模式在依赖项注入上略有变化。与其提供一个可以使用的对象,不如将其传递给一个知道如何定位(创建)要使用的对象的对象。

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

在这里,我们为应用程序对象传递了一个持久性对象。当执行保存/加载操作时,它将使用持久程序创建一个实际上知道如何执行该操作的对象。注意:持久性还是一个接口,您可以根据情况提供不同的实现。

potentially每次实例化动作时都需要唯一对象时,此功能很有用。

我个人认为这在编写单元测试中特别有用。

模式说明:

设计模式本身就是一个巨大的课题。这绝不是可用于帮助进行松耦合的专用模式列表。这只是一个共同的起点。

凭经验,您将意识到您已经在使用这些模式,只是您没有使用它们的正式名称。通过标准化他们的名字(并使每个人都学习它们),我们发现交流想法变得容易和快捷。


3
@Darren Young:感谢您接受它。但是您的问题只有三个小时了。我会在一天左右的时间内回来,以确认其他人没有提供更好的答案。
马丁·约克

感谢您的出色回应。关于关注点的分离...。我有一些需要使用某些数据的类,然后对该数据进行一些处理。过去,这就是我设计班级的方式。因此,最好创建一个类来获取数据并使它适应正确的形式,然后再创建一个类来实际使用数据吗?
达伦·杨

@Darren Young:与所有编程一样,该行是灰色且模糊不清。不阅读您的代码很难给出准确的响应。但是当我谈论时,managing the data我指的是变量(而不是实际数据)。因此,需要管理指针之类的东西,以免泄漏。但是可以注入数据,也可以抽象化数据的提取方式(以便您的类可以与其他数据检索方法一起重复使用)。对不起,我不能更精确。
马丁·约克

1
@Darren Young:正如@StuperUser在回答中指出的那样。不要minutae of loose coupling太过分(又称“这个词minutae”)。编程的秘诀是学习何时使用这些技术。过度使用可能导致纠结的代码。
马丁·约克

@Martin-感谢您的建议。我认为这就是我目前正在努力的地方.....我倾向于不断担心代码的体系结构,并试图学习我正在使用C#的特定语言。我想这将伴随经验和学习这些东西的愿望。感谢您的评论。
达伦·杨

6

我是ASP.NET开发人员,因此对WinForms耦合了解不多,但是对N-Tier Web应用程序了解甚少,假定UI,域,数据访问层(DAL)的三层应用程序体系结构。

松耦合是关于抽象的。

正如@MKO指出的,如果您可以用另一个组件替换(例如,使用您的Domain项目的新UI项目,保存到电子表格而非数据库中的新DAL),则存在松散耦合。如果您的域和DAL依赖于链下的项目,那么耦合可能会更松散。

松散耦合的一个方面是您是否可以用实现相同接口的另一方面替换对象。它不依赖于实际的对象,而是对其功能(接口)的抽象描述
松散的耦合,接口和依赖性注入器(DI)和控制反转(IoC)对于可测试性设计的隔离方面很有用。

例如,UI项目中的对象调用Domain项目中的Repository对象。
您可以创建一个假的对象实现了相同的接口库,根据测试使用的代码,然后编写测试(特殊行为的存根,以防止生产代码,保存/删除/被调用和嘲笑充当存根跟踪用于测试目的的伪造对象的状态)。
这意味着唯一被调用的生产代码现在仅在您的UI对象中,您的测试将仅针对该方法,并且任何测试失败会将缺陷隔离到该方法。

此外,在VS中的“分析”菜单中(取决于您使用的版本),有一些工具可以为您的项目计算代码度量,其中一种是“类耦合”,MSDN文档中将提供有关此信息的更多信息。

不要TOO在松耦合的minutae越陷越深,如果有,虽然,没有机会,事情得到重用(与一个以上的UI如域项目)和产品生命小,则松散的耦合变得更少优先级(仍会考虑在内),但仍将由架构师/技术负责人负责审核您的代码。


3

耦合是指一类对另一类拥有的直接知识的程度。这并不意味着将其解释为封装与非封装。它不是指一个类对另一类的属性或实现的了解,而是对另一类本身的了解。当从属类包含直接指向提供所需行为的具体类的指针时,就会发生强耦合。在不需要更改依赖类的情况下,不能替换依赖项或更改其“签名”。当从属类仅包含指向接口的指针时,就会发生松散耦合,然后可以由一个或多个具体类实现该指针。依赖类的依赖关系是接口指定的“合同”。实现类必须提供的方法和/或属性的已定义列表。因此,实现该接口的任何类都可以满足从属类的依赖关系,而不必更改该类。这样可以扩展软件设计。可以编写一个实现接口的新类来替换某些或所有情况下的当前依赖,而无需更改依赖类;新旧班级可以自由互换。强耦合不允许这样做。 参考链接。


3

看看5个SOLID原则。遵循SRP,ISP和DIP将大大降低耦合,DIP是迄今为止功能最强大的耦合。这是已经提到的DI之下的基本原理。

另外,GRASP值得一看。抽象概念(首先会发现很难实现)和具体模式(可能实际上会有所帮助)之间是奇怪的混合,但是美丽可能是目前您最不关心的问题。

最后,作为通用技术的切入点,您可能会发现有关IoC的这一部分非常有用。

实际上,我在stackoverflow上发现了一个问题,在此我演示了SOLID在具体问题上的应用。可能是一个有趣的读物。


1

根据维基百科:

在计算和系统设计中,一个松散耦合的系统是其中的每个组件都很少或不了解其他单独组件的定义的系统。

紧密耦合的问题使得很难进行更改。(许多作者似乎认为这主要是在维护期间引起问题,但以我的经验,这在最初的开发过程中也很重要。)在紧密耦合的系统中往往发生的情况是,对系统中一个模块的更改需要进行其他更改。在它所耦合的模块中。通常,这需要对其他模块进行更多更改,等等。

相反,在松散耦合的系统中,更改是相对隔离的。因此,它们的成本较低,并且可以放心制造。

在您的特定示例中,事件处理内容确实在GUI和基础数据之间提供了某种分隔。但是,听起来确实好像您可以探索其他领域。如果没有关于您的特定情况的更多详细信息,则很难具体说明。但是,您可能会从以下三层架构开始:

  • 数据存储和检索逻辑
  • 商业逻辑
  • 运行UI所需的逻辑

需要考虑的一点是,对于只有一个开发人员的小型一次性应用程序,在每个级别强制实施松散耦合的好处可能不值得付出。另一方面,必须由多个开发人员组成的大型更复杂的应用程序。最初,引入抽象并教育不熟悉有关其体系结构的代码的开发人员会产生成本。但是,从长远来看,松散的联轴器所提供的优势远远超过了成本。

如果您认真设计松耦合系统,请阅读SOLID原理和设计模式。

然而,重要的是要意识到这些模式和原则就是模式和原则。它们不是规则。这意味着需要务实,智能地应用它们

在涉及模式的地方,重要的是要了解,任何模式都没有单一的“正确”实现。它们也不是用于设计自己的实现的cookie切割器模板。它们在那里告诉您一个好的解决方案可能具有什么形状,并为您提供一种与其他开发人员交流设计决策的通用语言。

祝一切顺利。


1

使用依赖注入,策略模式和事件。通常,请阅读设计模式,它们都是关于松散耦合和减少依赖性的。我想说事件依赖于松散耦合,而依赖注入和策略模式需要一些接口。

一个好的技巧是将类放置在不同的库/程序集中,并使它们尽可能少地依赖于其他库,这将迫使您重构以使用较少的依赖项


4
安全套是一些抽象的IT术语吗?还是我根本不了解双关语?
达伦·扬

3
这是依赖项注入容器的另一个名称。
Mchl 2011年

2
也是防止病毒传播的好方法。我们在谈论同一件事吗?
sova,2011年

1
后端通常会进行重耦合
Homde 2011年

1
设置标题几乎不会滥用格式
Homde 2011年

0

让我提供另一种观点。我只是认为每个类都是一个很好的API。方法的调用顺序是显而易见的。他们所做的是显而易见的。您已将方法数量减少到所需的最少数量。举些例子,

初始化,打开,关闭

setTheFoo,setBar,initX,getConnection,关闭

第一个很明显,看起来很不错的API。如果以错误的顺序调用第二个命令,则可能会导致错误。

我不必担心必须修改和重新编译调用方。我维护了很多代码,其中一些是新的,也有15年的历史。进行更改时,通常会出现编译器错误。出于这个原因,有时我会故意破坏API。它使我有机会考虑每个呼叫者的后果。我不是依赖注入的忠实拥护者,因为我希望能够在视觉上跟踪没有黑盒的代码,并且希望编译器捕获尽可能多的错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.