关联,聚合和组合的用途是什么?


20

我研究了很多关于封装的理论以及实现封装的三种技术,即关联,聚合和组合。

我发现的是

封装形式

封装是一种将类中的字段设为私有并通过公共方法提供对字段的访问的技术。如果某个字段被声明为私有,那么该类之外的任何人都无法访问该字段,从而将这些字段隐藏在该类中。因此,封装也称为数据隐藏。

封装可以描述为一种保护性屏障,可以防止该代码和数据被该类外部定义的其他代码随机访问。通过接口严格控制对数据和代码的访问。

封装的主要好处是能够修改我们已实现的代码,而不会破坏其他使用我们代码的人的代码。通过此功能,封装使我们的代码具有可维护性,灵活性和可扩展性。

协会

关联是一种关系,其中所有对象都有其自己的生命周期,并且没有所有者。让我们以老师和学生为例。多个学生可以与一个老师联系,一个学生可以与多个老师联系,但是对象之间没有所有权,并且都有自己的生命周期。两者都可以独立创建和删除。

聚合

聚合是协会的一种特殊形式,其中所有对象都有其自己的生命周期,但是有所有权,子对象不能属于另一个父对象。让我们以一个系和一个老师为例。一个老师不能属于多个部门,但是如果我们删除该部门,则该教师对象不会被销毁。我们可以将其视为“具有”关系。

组成

组合还是聚集的一种特殊形式,我们可以称其为“死亡”关系。这是一种很强的聚合。子对象没有生命周期,如果删除父对象,则所有子对象也将被删除。让我们再次以房屋与房间之间的关系为例。房屋可以包含多个房间,但是没有一个房间的独立生活,任何房间都不能属于两个不同的房屋。如果我们删除房屋,房间将被自动删除。

问题是:

现在这些都是真实的例子。我正在寻找有关如何在实际的类代码中使用这些技术的描述。我的意思是,使用三种不同的封装技术有什么意义如何实现这些技术以及如何选择适用的技术。


1
请注意,聚合,组合和关联不是实现封装的技术。即使只有一个类/对象,也可以存在一种封装。封装只是“对象定向”中隐藏对象数据的基本功能。
Maxood

1
真的“一个老师不能属于多个部门”吗?您是否没有注意到您的资源修改了内容?
KNU

在进行域驱动设计时,我设法使用Composition标识DDD聚合根及其实体。旧约定的新用法。
琼(Jonn)

Answers:


12

正如您所描述的,关联,聚合和组合之间的区别可以追溯到手动内存管理的旧时代。例如,在C ++中,必须手动释放对象使用的内存,因此仔细设计组合对象的生命周期至关重要。尽管许多教科书仍在教导聚合与合成之间的区别,但是在具有自动内存管理功能的环境中进行编程时,本质上无关紧要。如果您有垃圾收集,那么所有这些都只是组成,期限。

另一方面,封装是比您描述的要通用得多的原理。首要的想法是在一个模块中捆绑数据和对该数据进行操作的功能。一种实现方法是保持模块状态为私有,并通过公共服务将更改暴露给该状态。因此,客户端无法自行访问状态,而必须通过发送消息来告知模块其意图。因此,封装不仅限于对象,还适用于服务。实际上,查看对象的一种方法是将它们视为服务。

这是封装的一个例子

public class Counter {
    private int n = 0;
    public int inc() { return n++; }
}

或使用lambda函数相同

var counter = (function() {
    var n = 0;
    var inc = function() { return n++; }
    return inc;
})();

在这两种情况下,数据(即变量n)都与在inc其上操作的函数捆绑在一起。而且其他任何功能都无法访问n,因此我们有一个封装的模块,可以将计数作为服务提供。

注意:通过访问器公开对象的所有内部状态实际上是对封装的违反。las,这是一种常见的违规行为,许多人会将其与良好的面向对象设计相混淆。


2
自动内存管理与对象关系无关。对象图本身告诉程序员如何根据所考虑的系统要求对类,其接口和方法进行编码。
Maxood

2
内存之外还有一个考虑因素。在数据设计或序列化中。在合成中,您可能需要在RDBMS中使用不同的外键约束,或者如果对象具有复合子对象与聚合子对象或关联对象,则可能需要更改处理对象序列化的方式。
克里斯(Chris

8

封装是一种将类中的字段设为私有并通过公共方法提供对字段的访问的技术。如果某个字段被声明为私有,那么该类之外的任何人都无法访问该字段,从而将这些字段隐藏在该类中。因此,封装也称为数据隐藏。

    public class Test{

    private String name;

       private int age;

       public int getAge(){
          return age;
       }

       public String getName(){
          return name;
       }
    }

转问

关联表示对象之间的关系。例如:计算机使用键盘作为输入设备。

当一个对象希望另一个对象为其执行服务时,将使用关联。

聚集是关联的特例。对象之间的方向关联。当一个对象“具有”另一个对象时,它们之间就会聚合。

例如:房间有桌子,但是桌子可以不带房间而存在。

    class Room {

      private Table table;

      void setTable(Table table) {
        this.table = table;
      }

    }

组合是聚合的特例。组成更具限制性。当两个对象之间存在合成时,没有另一个对象就无法存在合成对象。聚合中不存在此限制。例如:一间屋子里的房间,在屋子的生存期之后就不存在了。

    class House {

      private  Room room;

      House(Room roomSpecs) {
        room = new Room(roomSpecs);
      }

    }

组合是一种通过继承或对象组合在类中实现具有关系的设计技术,以实现代码重用。

Java编程的最佳实践之一是在继承上使用组合


这如何回答所提问题?
蚊蚋

1

使用这些技术通常会导致诸如SOLID或各种设计模式之类的设计实践。

使用模式,实践等的目的是描述对特定问题的解决方案,该解决方案也是可维护和可扩展的。您只需要获得足够的经验就可以告诉您在哪里使用哪种模式或技术。


1

坦率地说,我在学术界教授的这些概念在面向对象和类设计的上下文中确实具有重要性。从头开始为系统建模时,这些概念对我们有很大帮助。关联,聚合和组合完全属于UML的类图,并且完全独立于诸如内存问题之类的技术约束。

此外,您还必须考虑要建模的系统的更高级别或业务目标。我们的系统中正在考虑“房屋”和“房间”之类的对象,但不能紧密关联(通过组合)。例如,如果我要对房地产系统进行建模,则可能必须知道什么房间属于什么房子。但是,让我在建立一个调查或人口普查系统的模型时,我想知道在某个区域的房屋的每个房间中有多少人居住,那么我就不需要通过构图将房屋与房屋联系起来。

另一个例子可能是果园和某种水果。可以说,只有在果园里种上苹果树时,我才能考虑果园。最重要的是,整个系统的要求确实很重要。

封装是面向对象设计的支柱之一。您需要捆绑数据以及将对数据执行的操作。同样,您还必须从外部世界中隐藏对象的某些属性,以使该对象能够在有效状态下生存。当2个对象交互时,它们必须通过接口相互交互。这就是封装在设计OO系统时所确保的。

这些概念如何应用于代码:

关联关联表示对象之间的关系。它使程序员知道在其类中编写哪些方法以使它们彼此交互。您可以找到几个代码和类图示例来了解关联。在您的教和学生的例子,有一个关系教学通过教。因此,您只需编写一组方法(技术上称为接口),即可通过该方法了解哪个学生有哪些老师,以及哪个老师有哪些学生。关联还使系统建模者可以帮助数据库设计人员了解需要保留在数据库中的属性和字段。

组成: 如果一个对象是另一个对象的组成部分,那么我可能不得不在另一个对象的构造函数中指出这种关系。例如,在您的房屋和房间场景中,如果我们想知道哪个房间属于哪种房屋类型,我们可以编写以下代码。

class House{
          string _HouseType;   
     public:    
    void setHouseType(string house_type)
     {
        this. _HouseType = house_type;
     } 

     string getHouseType()
    {
       return _HouseType;
    }
};



 House HouseObject = new House();


class Room{

 public: 
 Room(string HouseType) {
       this._HouseType = HouseObject.getHouseType();  //as in my system a room cannot exist without a house

 } 

};

程序员在调用对象的析构函数时还将确保另一个对象的析构函数也被调用。这很关键。

聚合: 让我们说,如果对象之间的关系很弱,那么对于程序员来说,这将意味着使用实例变量来表示这种关系。然后编写一个mutator函数(setter),以从另一个对象为该对象提供价值。

class Department{

 string dept_name;

public:
   void setDeptName(string name)
   {
        this.dept_name=name;
   }

   string getDeptName()
   {
        return dept_name; 
   }

};



 Department DepartmentObject = new Department();

class Teacher{

 string dept_name;

public:

  setDeptName(string name)
  {
     this.dept_name = DepartmentObject.getDeptName();  //You only need to invoje this method when needed (aggregation)
  }
}

};

1

好的,让我们将其映射到某些核心属性,而不是仅在您理解了含义之后才有意义的抽象概念。像一些评论者一样,我不同意接受的答案,我说这些是独立于内存管理的概念。

封装形式

您想对客户端隐藏复杂性,仅从客户端的角度发布重要的内容,从而使客户端更轻松。作为奖励,您可以确定封装的代码不会混乱。只要尊重界面和功能,您就可以重做工作并放心,您不会破坏任何东西。依赖关系仅在发布的接口上。

封装是面向对象的主要支柱之一。它不是一种模式,它是一种原理,并且可能适用于逻辑和数据。首先使用类只是一个基本好处,而不是在图表或设计文档中明确指出的内容。

协会

这是一个非常宽松的概念,基本上只描述对象之间的依赖关系。一个对象知道另一个对象的存在,并可以在某个时候使用其功能。在图中,关联会警告您存在依赖性,更改一个对象可能会影响另一个对象。当您要解决一些问题时,这不是一种适用的技术,它更像是您应该意识到的生活事实。这是一种关系。就像具有Orders属性的发票一样。订单和发票都有自己的生命周期。一种是关于商品,另一种是关于付款,这本质上使它们独立,但是了解要付款的商品很重要。

遏制

我要添加它,因为它属于该系列,将使聚合更有意义。我再也听不到该词在SE环境中的使用了,但我认为它仍然有用。包含意味着封装,但严格来说是包含类所专有的对象实例。通过公共接口有选择地公开包含对象的功能。包含类控制受控对象的生命周期。当您需要现有类的某些功能以使包含的类起作用时,可以使用此功能。这可能是XML解析器,并且包含类的客户端可能永远不会看到或知道与XML相关的任何信息。作为一个比喻,可以将包含的对象视为后台办公人员。客户从没有见过这些人,但需要他们提供服务。

聚合

除了生命周期控制和聚合对象的可见性外,这与遏制非常相似。聚合的对象已经在不同的上下文中可用,并且由不同的实体进行管理。聚合器仅提供外观,即聚合对象的门户。当客户端处理聚合时,它将获取聚合对象本身的接口,而不是其周围的包装器。集合的要点是提供事物的逻辑分组。考虑服务或其他包装对象的访问点。

组成

在我看来,这是一个更现代的“围堵”一词,可能是因为它是在一本较新的近代流行书中创造的。在包含关注对象关系的技术方面的情况下,通常在设计决策的上下文中使用组合,更具体地说,将其用作继承的更灵活替代方法。

它并没有过多说明对象关系或所有权的性质,仅表示通过组合现有类的功能来实现功能。因此,我认为它不属于本系列,因为它没有说明其他实现的技术方面。

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.