Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Language agnostic 在超类中导致意外行为的子类—;面向对象设计问题_Language Agnostic_Oop_Overriding_Subclass - Fatal编程技术网

Language agnostic 在超类中导致意外行为的子类—;面向对象设计问题

Language agnostic 在超类中导致意外行为的子类—;面向对象设计问题,language-agnostic,oop,overriding,subclass,Language Agnostic,Oop,Overriding,Subclass,虽然我是用ObjC编码的,但这个问题是故意不可知语言的——它应该适用于大多数OO语言 假设我有一个“Collection”类,我想创建一个从“Collection”继承的“FilteredCollection”。过滤器将在对象创建时设置,从它们开始,类的行为就像一个“集合”,过滤器应用于其内容 我用明显的方式和子类集合做事情。我覆盖了所有的访问器,并且认为我已经完成了一项相当出色的工作——我的FilteredCollection看起来应该像一个集合一样,但是其中的对象与我的过滤器相对应,并被过滤

虽然我是用ObjC编码的,但这个问题是故意不可知语言的——它应该适用于大多数OO语言

假设我有一个“Collection”类,我想创建一个从“Collection”继承的“FilteredCollection”。过滤器将在对象创建时设置,从它们开始,类的行为就像一个“集合”,过滤器应用于其内容

我用明显的方式和子类集合做事情。我覆盖了所有的访问器,并且认为我已经完成了一项相当出色的工作——我的FilteredCollection看起来应该像一个集合一样,但是其中的对象与我的过滤器相对应,并被过滤给用户。我想我可以愉快地创建FilteredCollection,并将它们作为集合传递给我的程序

但我来测试,哦,不,它不起作用。深入研究调试器,我发现这是因为某些方法的集合实现正在调用重写的FilteredCollection方法(例如,有一个“count”集合在迭代其对象时所依赖的方法,但现在它得到的是过滤计数,因为我重写了计数方法以给出正确的外部行为)

这里怎么了?尽管OO“应该”以这种方式工作,但为什么感觉有些重要的原则被违反了呢?这个问题的一般解决方案是什么?有吗


顺便说一句,我知道这个问题的一个好的“解决方案”是在将对象放入集合之前对其进行过滤,而不必更改集合,但我要问的是一个更一般的问题——这只是一个示例。更普遍的问题是不透明超类中的方法依赖于子类可以更改的其他方法的行为,以及如果您想对对象进行子类化以更改这样的行为,该怎么办。

您的
FilteredCollection
感觉不对。通常,当您拥有一个集合并向其中添加新元素时,您希望它的
count
增加1,并且新元素被添加到容器中


您的
FilteredCollection
不是这样工作的-如果您添加一个已过滤的项目,容器的
计数可能不会更改。我想这就是你的设计出错的地方


如果该行为是故意的,那么
count
的契约将使其不适合您的成员函数试图使用它的目的。

您继承的
集合具有特定契约。类的用户(包括类本身,因为它可以调用自己的方法)假定子类遵守约定。如果幸运的话,合同在其文档中有明确的说明

例如,契约可以说:“如果我添加一个元素
x
,然后在集合上迭代,我应该返回
x
”。您的
FilteredCollection
实现似乎违反了这一约定

这里还有另一个问题:
集合
应该是一个接口,而不是具体的实现。实现(例如,
TreeSet
)应该实现该接口,当然也要遵守其合同


在这种情况下,我认为正确的设计不是从
Collection
继承,而是将
FilteredCollection
创建为围绕它的“包装器”。可能
FilteredCollection
不应该实现
Collection
接口,因为它不遵守通常的集合契约。

您所描述的是多态性的一个怪癖。因为您可以将子类的实例作为父类的实例来处理,所以您可能不知道封面下面是什么样的实现

我认为您的解决方案非常简单:

  • 您声明不修改集合,您只在人们从集合中获取时对其应用筛选器。因此,您应该而不是重写计数方法。所有这些元素都在集合中,因此不会对调用方撒谎
  • 您希望base.count方法正常运行,但仍然希望使用count,因此应该实现一个
    getFilteredCount
    方法,该方法返回过滤后的元素数量

  • 子类化是关于“类”关系的。您所做的并不是超出标准,但也不是最标准的用例。您正在对集合应用筛选器,因此可以声称“FilteredCollection”是一种集合,但实际上您并不是在修改集合;你只是用一个简化过滤的图层来包装它。无论如何,这应该是可行的。唯一的缺点是你必须记住调用'getFilteredCount'而不是.getCount

    我认为真正的问题是对面向对象语言应该如何工作的误解。我猜您的代码看起来是这样的:

    Collection myCollection = myFilteredCollection;
    
    并期望调用集合类实现的方法。对吗

    在C++程序中,如果集合中的方法不被定义为虚拟方法,那么这可能是有效的。但是,这是C++设计目标的一个工件。 在几乎所有其他面向对象语言中,所有方法都是动态调度的:它们使用实际对象的类型,而不是变量的类型


    如果这不是你想知道的,那就读一读这本书,问问自己是否打破了它。很多类层次结构都是这样做的。

    与其对集合进行子分类以实现FilteredCollection,不如尝试将FilteredCollection作为一个单独的类来实现,该类实现iCollection并委托给现有集合。这类似于“四人帮”的故事

    Partia
    class FilteredCollection implements ICollection
    {
        private ICollection baseCollection;
    
        public FilteredCollection(ICollection baseCollection)
        {
            this.baseCollection = baseCollection;
        }
    
        public GetItems()
        {
            return Filter(baseCollection.GetItems());
        }
    
        private Filter(...)
        {
            //do filter here
        }
    
    }