设计模式问题

7
我是一个设计模式的新手,这是我的问题:
如果我们有一个抽象类,并且有几个类实现它,每个类都有不同的属性。 现在我有另一个(Manager类)持有一个抽象类的数组,并希望在其中放置一个搜索方法...如何在不转换为具体类的情况下实现呢?
我的想法有两个:
第一种:添加额外的接口(即不使用具体类而是用接口进行强制转换),这符合代码与接口而非实现的规则...但是当我添加另一个类时,我必须为其创建一个新接口并同时编辑manager(客户端),这似乎不太好。
第二种解决方案: 看起来有点奇怪,并且仍需要改进,但它的主要目标是使manager或任何其他客户端能够使用抽象类而不必知道是谁继承它或其属性。
解决办法如下: 每个添加的新项都必须覆盖一个接口,该接口强制其生成其字段的完整描述,例如,汽车对象将必须返回 一个哈希映射,其中包含以下内容
字段:{fieldType,fieldValue}
例如:
model:{text,“福特”} manifactureDate:{Date,“12/1/89”}
每个对象还必须实现一个名为compareFields的方法,该方法接受这样一个哈希映射并将其与其字段进行比较,并返回true或false。
现在,通过这种方式,我已经解决了许多问题 -对于GUI,我只需要为此哈希映射创建呈现引擎即可显示任何项,而不必知道其类型。 (GUI也是抽象类的另一个客户端) -对于搜索,我可以获得包含用户在搜索表单中输入的字段的哈希映射,并循环处理抽象项并调用compare fieldmethod
我仍然不知道如何处理复杂对象(其具有另一个对象作为其属性) 我不知道这是什么模式...这只是我想到的一个想法。
编辑:具体示例
如果我有一个抽象项目类,其中汽车、公交车和船只实现它,每个类都有不同的属性....例如,Traffic Manager如何使用抽象类搜索特定项,而无需转换为汽车或公交车... 非常抱歉问题很长。

1
无论他使用什么编程语言,设计模式并不在意,它们可以在大多数编程语言中实现。 - JonH
2
实际上,语言选择确实很重要。在动态类型语言中,这不是问题。但在静态类型语言中,这就成为了一个问题。 - R. Martinho Fernandes
不清楚你在做什么或者你的问题是什么。如果你提供一个包含你的类和拟议搜索函数的代码示例,可能会更清晰明了。 - RossFabricant
1
你想要搜索的起始集合/列表/数组列表的类型是什么?如果你正在一个多类型的集合/列表/任何东西(无论是“接口”还是“抽象”或者是“对象”)中搜索特定类型(如“汽车”,“公共汽车”等),那么这就是一种代码异味。 - Alex Bagnolini
是的,它将是公交车和汽车的数组... - Ahmed Kotb
显示剩余8条评论
8个回答

9

封装

面向对象设计的封装原则指出,对象不应该将其状态暴露给外部。如果你的对象暴露了内部信息,那么它就破坏了封装性。根据面向对象的设计,在允许对象进行决策的情况下,将搜索条件传递给对象仍然是可以的。

interface IVehicle{
   bool doesMatch( Map<String,String> searchCriterion )
}

你可以在所有车辆上使用迭代器,并检索匹配的车辆,而不破坏封装性。车辆的特定实现仍然可以按需重新实现。
访问者
否则,我建议您查看访问者模式。其基本思想是遍历所有对象,由一个额外的类处理每个特定类型的处理。这也会破坏纯封装性(因为对象需要向访问者公开其数据),但它更加优雅。
class VehicleSearchVisitor
{
   Map<String,String> searchCriterion;
   void visit( Car car ) {...}
   void visit( Bike bike ) { ... }
   ....
}

元编程

自我描述的对象是另一个概念,称为元编程。然后表示层会内省其他对象以了解如何处理它们。这传统上被认为是高级面向对象技术。您可以创建自定义注释来描述类的字段,以便表示层可以动态呈现适当的标签。例如,Hibernate 注释使用相同的思想。需要小心地进行元编程,否则会遇到其他问题。

instanceof

使用 instanceof 也是一种内省形式(因为您要求对象提供其类),通常不鼓励使用。不是因为它本身有错,而是因为它往往被滥用。只要可能,就依赖于传统的面向对象原则。过度使用 instanceof 是一种 代码坏味道

总之,我建议使用访问者进行搜索,并且不要在表示层中使用元编程,而是为每种类型的车辆创建一个简单的页面。


1
我本来想推荐第一个答案 - 对我来说这是最有意义的。这样,每个IVehicle都可以决定它是否符合搜索条件。 - Daniel Yankowsky

6

好的,所以你是在扩展类而不是实现接口。如果你有Bus、Car、Truck、Train等类,并且它们都实现了要求返回可排序/可搜索值的 IVehicle 接口中的函数,那么你就可以将它们都引用为 IVehicle 类型,并在所有对象上调用该方法。

ActionScript 3 代码:

package com.transportation.methods {
  public interface IVehicle {
    function getSpeed():Number;
    function getOtherSortableOrSearchableValue():*;
  }
}

并且

public class Car extends Sprite implements IVehicle

您需要在Car类中定义getSpeed()和getOtherSortableValue()函数,并且可以将其称为Car或IVehicle。由于我示例中的所有交通工具都将实现IVehicle,只要您将它们引用为IVehicles,就可以调用这两个函数。


这是我的第二种方法(哈希映射方法),不是吗? - Ahmed Kotb
这实际上忽略了原始规定之一,即每辆车可能具有不同的属性。因此,汽车可能会有getSpeed(),但是弹跳球可能没有。弹跳球可能会有getBounceHeight(),但对于汽车来说这毫无意义。哈希映射的想法可以处理这些情况,但是这个特定的答案则不能。 - Daniel Yankowsky
好的,可以轻松地实现一个叫做“比较”的函数,它接受某种输入,每个类都知道如何将自己与其进行比较,就算我在回答中没有定义这个函数,也并不意味着它不可能存在。 - Aaron
值得注意的是,被标记为接受答案的访问者模式并不能在ActionScript 3中实现,因为您无法定义具有相同名称的多个函数 - 它们需要具有不同的名称,这会破坏原始请求,即不需要知道正在查看哪种类型,并且由于原始问题没有指定语言,因此在我选择的语言中仍然是一个很好的解决方案。 - Aaron
@Tegeril 访问者模式在任何语言中都是可行的。如果语言是类型化的,则可以根据参数类型区分visit()方法。在非类型化语言中,您需要将它们命名为visitTypeA()、visitTypeB()等,并相应地更改accept()方法。请参见http://www.as3dp.com/2008/12/06/actionscript-30-visitor-design-pattern-a-tale-of-traverser-and-the-double-dispatch-kid/。 - ewernli
显示剩余2条评论

4
你似乎在描述Steve Yegge所谓的通用设计模式
在GUI中使用键值对可能在简单情况下有效,但很难让其看起来好看。除非你的对象层次结构很深并且必须非常可扩展,否则你可能想为每个具体类别创建一个单独的表单,这样做的工作量更小,而且看起来更好。你仍然可以重复使用共享的GUI组件,因此添加新的具体类别应该相当简单。

2

这似乎是一种“集中式”仓储模式。我认为这不是一个好主意。

你建议的做法是,不要有一个通用搜索的中心化方式,而应该有几个专门的仓库:一个用于汽车,一个用于船只,一个用于飞机等。

每个仓库都知道各自对象的详细信息。然后,您可以将这些专门的仓库之间的常见操作因素提取到一个仓库基类或接口中。当您不关心细节时,使用基本仓库接口。当您关心细节时,请使用专门的仓库。

以下是Java中的一个简单示例(为了简洁起见,我略去了getter / setter - 不要使您的字段公开):

interface Vehicle { int id; int maxSpeed; }
class Car implements Vehicle { int doors; int id; int maxSpeed; }
class Boat implements Vehicle { int buoyancy; int id; int maxSpeed; }
class Plane implements Vehicle { int wingSpan; int id; int maxSpeed; }

interface VehicleRepository<T extends Vehicle> {
    T getFastest();
    T getSlowest();
    T getById(int id);
}

interface CarRepository inherits VehicleRepository<Car> {
    List<Car> getCarsWithTwoDoors();
}

interface BoatRepository inherits VehicleRepository<Boat> {
    Boat getMostBuoyantBoat();
}

interface PlaneRepository inherits VehicleRepository<Plane> {
    List<Plane> getPlanesByWingspan();
}

是的,那是我的第一个建议...通过创建额外级别的接口,我认为唯一的问题在于当我添加另一个项目,比如火箭时,我将不得不创建一个火箭存储库,对吧? - Ahmed Kotb
是的。我认为这没有任何问题,因为您不必更改现有类以添加火箭。如果使用集中的东西,每次添加其他东西时都必须更改它。 - R. Martinho Fernandes

2

这似乎是访问者模式的工作。您有一组抽象对象,但不知道每个对象是什么。使用访问者模式,您可以迭代所有对象并执行特定于每个对象的操作。《GOF设计模式》提供了关于访问者模式的详细信息,但我将在此处提供一个良好的Java示例。

public class Vehicle {
    public void accept( VehicleVisitor visitor ) {
        visitor.visit( this );
    }
}

public interface VehicleVisitor {
    public void visit( Vehicle vehicle );
    public void visit( Car car );
    public void visit( Bus bus );
    public void visit( Truck truck );
    // Augment this interface each time you add a new subclass.
}

public class Car extends Vehicle {
    public void accept( VehicleVisitor visitor ) {
        visitor.visit( this );
    }
}

public class Bus extends Vehicle {
    public void accept( VehicleVisitor visitor ) {
        visitor.visit( this );
    }
}

public class Truck extends Vehicle {
    public void accept( VehicleVisitor visitor ) {
        visitor.visit( this );
    }
}

public class VehicleSearch implements VehicleVisitor {
    protected String name;
    public List<Vehicle> foundList =
        new ArrayList<Vehicle>();
    public VehicleSearch( String name ) {
        this.name = name;
    }
    public void visit( Vehicle vehicle ) {
        return;
    }
    public void visit( Car car ) {
        if ( car.getModel().contains( name ) ) {
            foundList.add( car );
        }
    }
    public void visit( Bus bus ) {
        if ( bus.getManufacturerModel().contains( name ) ) {
            foundList.add( bus );
        }
    }
    public void visit( Truck truck ) {
        if ( truck.getLineModel().contains( name ) ) {
            foundList.add( truck );
        }
    }
}

public class Manager {
    protected List<Vehicle> vehicleList;
    public List<Vehicle> search( String name ) {
        VehicleSearch visitor =
            new VehicleSearch( name );
        for ( Vehicle vehicle : vehicleList ) {
            vehicle.accept( visitor );
        }
        return visitor.foundList;
    }
}

乍一看,您可以简单地向Vehicle类添加一个搜索方法,并为列表中的每个成员调用该方法,但访问者模式允许您定义对车辆列表的多个操作,这样您就不必为每个操作在Vehicle类中添加新方法。
然而,访问者模式的一个缺点是当您添加要访问的新对象类时,访问者本身需要更改。在这个例子中,如果您向系统中添加了一个RocketShip车辆,则需要向访问者添加一个visit(RocketShip rocketShip)方法。
您可以通过创建一个VehicleVisitorTemplate并覆盖所有visit方法来减轻此问题。您的操作子类继承模板类,然后只需要覆盖所需的方法。当您需要添加新类和访问方法时,将其添加到接口和模板类中,然后除非必要,否则其他所有操作都不需要更新。

0

我不知道你在使用什么编程语言,但为什么不让抽象类实现接口呢?这样,只要所有具体类都继承自实现了接口的抽象类,就不会有任何问题。

如果你正在使用Java,那么类的层次结构应该如下所示:

public interface IVehicle {/*your code goes here*/}

public abstract class AbstractVehicle implements IVehicle{/*your code goes here*/}

public class Car extends AbstractVehicle{/*your code goes here*/}

当然,这些都会在不同的文件中定义。


如果我添加了一个新的船类,例如它具有与汽车不同且无法添加到车辆接口的属性...那么我该怎么办? - Ahmed Kotb
@Ahmed Kotb - 一种实现方法是在接口中添加一个getAttributes()方法,该方法返回一个HashMap,其中包含属性的键/值对。调用类可以查询HashMap以确定存在哪些属性(键),如果需要的话。您的search()方法可以在抽象类中定义,以在实际搜索发生之前进行此检查。 - ssakl
是的,这正是我在第二个建议中尝试做的事情,但我想知道是否有更好的方法或者这已经是一种设计模式,我可以阅读相关资料。 - Ahmed Kotb
@Ahmed Kotb - 你的第二个建议略有不同之处是,你的管理器类仍然可以只使用接口,但实现在抽象类中。你只需要用那个search()方法设计接口即可。 - ssakl

0
一个管理者,例如交通管理员如何使用抽象类搜索特定项目而不需要强制转换为汽车或公共汽车?
我会这样做:让抽象类有一个抽象方法,返回一个ID值(比如枚举值),表示对象是哪种具体类型,并让每个具体类重写该方法以返回其ID值。
或者我会尝试使用C++的RTTI工具或Python的“instanceof()”函数。
这有帮助吗?还是我回答了错误的问题?

所有这些都不是非常面向对象的解决方案,它们违反了LSP原则。 - R. Martinho Fernandes
1
当我没有其他选择时,我会这样做,因为使用instance-of意味着管理类必须知道具体的类,我认为这不是面向对象编程中最好的事情,正如马丁诺所说。 - Ahmed Kotb


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