OO设计的封装性

5

我有一个关于封装的问题。据我所知,封装可以使用私有/受保护的数据成员隐藏实现细节,并提供公共方法和属性来操作数据。这里的想法是防止类消费者直接修改数据成员。

但是,我对属性getter或其他返回私有/受保护数据成员的公共方法存在担忧。例如:如果我有这样的类

public class Inventory
{
    private List<Guitar> guitars = new List<Guitar>();

    public void AddGuitar(string serialnumber, string price)
    {
        Guitar guitar = new Guitar(serialnumber, price);
        guitars.Add(guitar);
    }

    public List<Guitar> GetGuitars()
    {
        return guitars;
    } 
}

现在,如果Inventory类的消费者调用GetGuitars方法,它将得到在Inventory类中维护的吉他清单。现在,消费者可以修改列表,例如删除、添加或修改项目。对我来说,这看起来像是我们没有封装。我认为应该在GetGuitars()方法中返回Guitar清单项的副本。你认为呢?
我的封装理解正确吗?
谢谢

这更或多或少是在决定是否严格遵守迪米特法则时面临的折衷。许多人认为,允许访问组合对象(就像你所做的那样)是实用的。像C ++这样的语言将允许GetGuitars()const的。 - StuartLC
防御性复制列表并不能帮助太多,只要吉他不是不可变的或者也没有进行防御性复制。遗憾的是,Java缺乏良好的不可变性支持。 - atamanroman
伙计们,他不是在问Java。应该是C#吧。 - Adam Dyga
6个回答

1

通过使用适当的接口限制对它们的访问,可以很好地封装对象列表。

我认为你通过AddGuitar方法控制列表的添加是正确的,因为你可以控制放入其中的内容。在我的看法中,你可以通过将GetGuitars更改为返回IEnumerable而不是List来加强这种设计。

这减少了调用者对列表的控制,同时也不承诺返回一个抽象类型。这样,你的内部数据结构可以改变,而公共接口也不需要改变。


1

你说得对。有了这样的setter,客户端就能够修改列表。如果添加吉他需要一些特殊处理,那么这是不希望的。在这种情况下,你有两个选择:

  1. 返回列表的副本(正如你已经建议的那样)。
  2. 在getter中使用ReadOnlyCollection进行包装。

这两种情况都应该在方法描述中进行记录,以便客户端在尝试外部修改列表时不会“惊讶”。


1

1
从风险的角度来看,如果您返回列表的副本或使其不可修改(在添加吉他时采用函数式编程风格创建一个全新的不可修改列表),确实更好。
从封装的角度来看,最好摆脱getGuitars()方法,然后Inventory类应该提供与之相关的功能(例如printInventoryReport()或其他)。这样,没有客户端类需要知道您如何存储吉他,您可以将相关代码保留在Inventory类中。权衡是这个类会变得更大,并且每次您需要从吉他列表中获取新内容时,都需要修改Inventory。
我推荐一篇好文章:http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html。虽然它在当时引起了很大争议,但我认为其中有很多真理。

如果您选择使用getter,一个小技巧是选择需要它是List还是Collection。甚至可以使用Iterable!这样做可以尽可能少地透露您的实现细节,从而实现更好的封装。


0

我认为返回列表在封装方面还有待改进。您可能需要考虑编写用于单个项目的getter,或者可能是一个迭代器。该列表似乎是实现细节,因此其他类没有直接访问它的必要。


0

这里至少有两个问题。

第一个问题是关于隐藏实现。您可以将“吉他”字段更改为数组或数据库,但可以保留AddGuitar和getGuitars方法的签名,以便客户端代码不会中断。

第二个问题是是否要返回吉他列表的防御性副本。一旦您拥有吉他清单,您是否想要添加和删除元素?由于您有一个添加吉他的方法,我认为不需要。


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