【问题标题】:Extending an already written class using OOPS使用 OOPS 扩展已经编写好的类
【发布时间】:2018-11-23 04:23:41
【问题描述】:

我正在努力学习设计实践和 OOP。我以停车场问题为例开始。

我有一个GeneralParkingLot 接口和一个Vehicle 接口。 GeneralParkingLot只有一个功能returnParkingLotSizeVehicle接口有多个Vehicle属性。

我创建了一个类 DowntownParkingLot,它扩展了 GeneralParkingLot 并具有其他属性,例如 listOfCarsavailableSlots 等,以及一个扩展了 Vehicle 类的 Car 类。

我有一个处理传入命令的HandlerClass,在该类中我已经声明了一个DownTownParkingLot 对象和多个函数来处理命令,所以在每个函数中我只传递DowntownParkingLot 的对象并对其进行操作。

我创建了不同的服务,例如CreateParkingLotObjectParkACarFreeASlot 等,这些服务由命令处理程序调用。

我还创建了单元测试来测试我的应用程序。

我的问题是,如果我想扩展我当前的停车场以具有额外的功能,比如让我们说多个楼层属性,或者如果我现在想处理多个停车场而不是一个,那么扩展我的 @ 的最佳方法是什么? 987654338@类或DowntownParkingLot类。我还阅读了有关适配器和装饰器模式的信息,但我认为当我从一开始就已经遵循特定的设计模式时,这些很有用,在我的情况下,我没有遵循任何特定的模式,那么扩展我的代码的最佳方法是什么。我问这个是因为有时我们会遇到一个不是根据任何设计模式制作的类并且在多个地方使用(比如很多 API 等),那么扩展此类代码的最佳方法是什么。从头开始重构是唯一的选择吗?或创建从旧类继承的新类?最好的方法是什么?另外我想尽可能多地使用已经创建的单元测试,而不是再次重写相同的测试用例。

【问题讨论】:

  • 因为你有一个extends 链,你必须决定新属性应该放在哪个位置。使用multiple floor attribute 将其添加到GeneralParkingLot 是最有意义的,因为(至少在理论上)每个丢失的停车位都可能有多个楼层,而不仅仅是DowntownParkingLots。如果您无法更改该类,则创建一个扩展GeneralParkingLot 的新“超级”类。现在您的所有类都可以扩展新的“超级”类并包含所有信息。希望我正确理解了您的问题
  • 如果我将楼层添加到GeneralParkingLot 那么要实现它应该创建一个新类还是在旧类中实现?考虑到将来还可以添加更多功能,我不知道哪个会更好。我也不想打扰当前的功能
  • 此外,它不仅没有添加地板,我还想知道在这种情况下我们如何扩展代码的一般想法?
  • 不要让它成为一个接口。让它成为abstract class(甚至只是一个普通的类。取决于用例)。如果您只有要公开的方法,而不是任何字段,则应使用接口
  • 您使用相同的DowntownParkingLot。但是您必须检查每个扩展类是否真的应该具有该属性,并且只有在这种情况下,您才能将该字段添加到抽象类

标签: java oop inheritance design-patterns interface


【解决方案1】:

继承对教科书很好,在实践中它是非常严格的。你应该使用接口和许多不同的实现:

interface ParkingLot {
    void park(Vehicle v); 
}

interface Vehicle {
     int size();
     boolean isDIsabled();
}

您只公开在最高级别操作绝对需要的内容。就像停车场停放一辆车。车辆只能放入停车位大小或残疾人停车位。停车场就是这样知道你的车停在哪里的。

然后每个实现类都会做它需要做的事情来使流程工作。我认为楼层是内部实现细节,可能不需要在 API 上公开,而是在内部使用。

还建议使用工厂根据地板、电梯、厕所等建造停车场......这样您就可以通过配置而不是硬编码来建造通用停车场。

您可以使用“命令”在停车场之间传递停车命令,直到第一个停车场为您的汽车找到合适的停车位。在这种情况下,您链接所有停车场并在它们之间传递请求。它也可以用于楼层之间的内部,因为每个楼层都会尝试找到从底层到屋顶的匹配项。

您可以使用与寻找停车位相关的功能“装饰”您的汽车。因此,您再次拥有一辆添加了不同功能的通用汽车。停车场设计也是如此。

当您通过停车命令时,停车场将读取装饰的特征并决定是否有停车位。

例如,网站的用户角色 (auth) 的安全性工作方式类似。

【讨论】:

    【解决方案2】:

    在你的情况下,你最顶层的类是一个接口,所以你不能直接添加属性。

    解决问题的一种方法是创建一个实现GeneralParkingLot 接口的顶级实现类:

    public abstract CommonParkingLot implements GeneralParkingLot {
         // add your missing attributes here
         protected int floorNo;
    }
    

    (如果您只想添加属性而不为GeneralParkingLot 提供一些默认实现,那么您可以省略implements 部分)

    然后让你的实现类扩展这个CommonParkingLot

    public class DowntownParkingLot extends CommonParkingLot implement GeneralParkingLot {
         // Now all missing attributes are accessible here
    }
    

    对于缺少的功能,default 函数接口应该会派上用场。 (关键字是指定的语言),您可以根据需要向GeneralParkingLot 接口添加更多功能,并为其添加默认主体。

    还有一点,您当前的实现有点限制为DowntownParkingLot,因为您没有向界面添加通用功能。我建议你重构 HandlerClass 类:

    public interface GeneralParkingLot {
        int returnParkingLotSize();
        ParkingLotObject createParkingLotObject();
        boolean parkACar();
        int freeASlot();
        default public void parkMultipleCar() {
             throws new UnsupportedOperationException();
        }
    }
    

    现在你的HandlerClass 应该在GeneralParkingLot 而不是DowntownParkingLot 上运行。

    通过这样做,您可以在不更改代码的情况下添加更多GeneralParkingLot

    【讨论】:

      【解决方案3】:

      我认为你必须在这里使用Visitor 模式。 Gang of Four 指定的visitor 模式的定义是,

      表示要对对象结构的元素执行的操作。 Visitor 允许您定义一个新的操作,而无需更改它所操作的元素的类。

      假设您保持ParkingLot 层次结构完整,您可以为每个必须添加的新操作定义新访问者。这会对您现有的类和单元测试产生最小的更改或没有更改。

      【讨论】:

        【解决方案4】:

        好的,这是我阅读了有关您问题的 cmets 后的回答:

        一个有几个楼层的市中心停车场显然也是一个市中心停车场。所以很明显,如果不是同一个类,我们将希望有一个代表从DowntownParkingLot 派生的MultiFloorDowntownParkingLot 的类。

        正如您所确定的,您已经实现了SingleFloorDowntownParkingLot,它也是DowntownParkingLot

        DowntownParkingLot 可以是单层或多层,因此它应该是抽象的并实现GeneralParkingLot 接口,并且具有与您当前的DowntownParkingLot 相同的方法(但它们的实现应该在SingleFloorParkingLot 中)。

        一个MultiFloorDowntownParkingLot 由多个SingleFloorParkingLots 组成,并且像每个方法的包装器或多路复用器一样工作。例如,listOfCars 只会连接其所有楼层返回的列表。

        此模型可以通过多种方式进行扩展:

        • 多个入口:DowntownParkingLot 可以有多个入口。单层实现了这一点,多层停车场组织了单层的哪些入口向外,哪些入口相互连接。
        • 不同的车位尺寸:“DowntownParkingLotcan have multiple slot sizes. ASingleFloorDowntownParkingLothas one slot size. TheMultiFloorDowntownParkingLot`具有其楼层的所有车位尺寸。

        需要注意的是,对于用户来说,只有DowntownParkingLot 是重要的。用户永远不需要知道特定的DowntownParkingLot 是单层还是多层(尽管在某些情况下它可能会受益,但这同样有效)。只有DowntownParkingLot 的构造函数必须决定他使用哪个子类。

        当然,还有很多其他的可能性,它们的可用性很大程度上取决于用例,但我认为这是相当普遍的。

        希望能帮到你!

        编辑:视觉插图!

        【讨论】:

          【解决方案5】:

          从头开始重构是唯一的选择吗?或创建从旧类继承的新类?最好的方法是什么?

          真的取决于您对未来变化的预期。 OO 设计一方面是尽可能廉价地解决当前需求,另一方面是预测未来可能发生的变化。

          例如,让我们以@Tobias 提供的MultiFloorDowntownParkingLot 为例。如果您预计将来您将拥有TollParkingLotTollMultiFloorParkingLot 以及各种变体,那么装饰器是首选,因为在您实施这些不同的停车场时,将收回更改代码以支持装饰器的成本。但是,如果您怀疑您的停车场将是 GeneralParkingLot 的简单变体,那么只需扩展 GeneralParkingLot 就足够了。

          要考虑的另一件事是composition vs inheritance。您也许可以利用组合来解决一些需求。例如,我可以将收费功能创建为一个新类,该类包装(或由其组成)GeneralParkingLost 对象(其中MultiFloorParkingLot 可以很容易地被此收费停车场对象包装)。

          很容易过度思考这一点。您可能必须对设计阶段进行时间限制,并采用最便宜的解决方案来解决问题并处理任何预期的变化。 Code Complete 一书很好地涵盖了这些主题。在那本书中,它试图列出程序的哪些部分可能会随着时间而改变,然后可以根据这些部分可能会改变的事实进行设计。

          除非您有明确的要求表明您需要装饰器,否则我会使用 @Tobias 回答和组合来填补任何空白并处理聚合(停车场集合不应是 GeneralParkingLot)。

          【讨论】:

            【解决方案6】:

            首先让我们在这里研究一些术语,以免以后混淆。这里我假设上下文是 Java,因为它被标记为 Java。

            1. 不要混合使用“class”和“interface”。类定义对象,接口定义行为。类不能扩展接口,只能实现它。但是,您可以使用一个接口来扩展另一个接口,从而添加更多行为。

            2. 不要混合使用“attribute”和“field”。对于一个类,属性通常意味着私有访问,而字段意味着通过 getter/setter 进行公共访问。

            其次,让我们研究 OOP 概念。 OOP 是对现实世界的抽象。您的应用程序设计的好坏完全取决于您对该领域的了解程度。

            现在让我们处理您的示例。您的案例中的主要概念只有一个:停车场。但是,在不同的领域,它意味着不同的抽象。

            在支付应用程序中,它关心两件事:空间和时间。剩下多少空地,它们在哪里?对于停车场内的每​​个单元,它停了多长时间,离开时会改变多少。

            在一个交通控制应用中,它有一个完全不同的视角:可以流多少流量,引导流量的最佳路线是什么。

            看,即使是非常简单的概念,不同的领域也会产生不同的设计。如果你发现自己在设计上绊倒了。尝试忘记类/接口或 OOP,只专注于首先了解您的领域,这在大多数情况下都能解决问题。

            让我们现在开始处理一些细节。 (因为我对你的领域不太了解,所以我会根据我的理解简单解释一下设计过程。你可以吸收这个想法并定制你的设计)。

            GeneralParkingLot: 因为它是一个接口,所以它定义了行为。停车场应该有哪些行为?好吧,底线:它应该告诉availableParkingSpaces,它应该能够park 一辆汽车和charge 当它离开时。现在我们定义了 3 个行为:

            int getAvailableParkingSpaces();
            int park(Vehicle v);
            int charge(Vehicle v);
            

            DowntownParkingLot: 这应该是你的对象,它具有属性并实现行为。

            class DowntownParkingLot implements GeneralParkingLot{
               private int totalSpaces;
               private int[] parking_slots;
               private Map<Integer, Vehicle> parkedVehicle;  //<park_space_id, vehicle>
            
               //implements of interface methods
            }
            

            MultiFloorParkingLot:我不确定这个多层会如何影响您的设计,因为它隐藏在您的班级中。 您的多层停车场在行为方面是否提供了额外的东西?如果有,应该是扩展GeneralParkingLot为的接口

            interface MultiFloorParkingLot extends GeneralParkingLot{
               //provide extra behaviours here
            }
            

            如果不是,应该是停车位存储方式不同的类,如

            class MultiFloorParkingLot implements GeneralParkingLot{
                private int[][] parking_slots;  // locate parking_id by floor + park_num
            
                //everything else should not changed much.
            }
            

            就我个人而言,我认为多层与单层没有什么不同。多层或单层是建筑结构的概念,仅当您的应用程序需要将车辆定位/引导到可用空间时才有用。无论是“4楼A列#24”还是“1区B列#25”在概念上都没有区别,区别在于实现,应该封装里面你的班级本身。但同样,这取决于您的域。

            Vehicle:既然是停车场用车的概念,我们不关心它的品牌、性能,或任何与车辆本身有关的东西。

            interface Vehicle {
                String getLicense();
                void park(GeneralParkingLot p);
            }
            

            Car:对于汽车,它至少应该有一个车牌,用作ID。

            class Car implements Vehicle{
                String licensePlate;
                //if your domain needs to track how long it parks
                long enterTimeStamp, exitTimeStamp; 
            
                @Override
                public void park(GeneralParkingLot p){
                    //do something that car needs to do, then register car on parking-lot
                    p.park(this);
                }
            }
            

            看到这里了吗?将汽车停入停车场的过程就是将汽车登记到停车场的过程。他们俩可能都需要做一些事情来完成这个过程。他们将分别做的事情不应该由外部班级完成。 开闭原则:你可以告诉我该做什么,但不能告诉我怎么做。这样可以灵活地改变不同停车场的不同汽车的细节。

            HandlerService: 我不确定这个处理程序服务将如何工作,但在 OO 概念中,行为应该由对象本身执行。让我们编码:

            class HandlerService {
                //let's use park a car as example. 
                //notice that your using interface instead of class.
                //programming on interface, not class
                public void parkACar(Vehicle v, GeneralParkingLot p){
                   v.park(p);
                }
            }
            

            无论如何,编程只是解决问题的工具。而OOP只是一种抽象方式。一个好的、灵活的设计取决于你的领域知识,而不是编程技能。因此,善待您的业务分析并与他们成为朋友。 ^-^

            希望对您有所帮助。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-01-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多