2

我正在尝试编写一个简单的程序,该程序应允许用户保存和显示异构但以某种方式相关的数据集。为清楚起见,我将使用具有代表性的车辆示例。程序流程是这样的:

  1. 该程序创建一个车库对象,它基本上是一个可以包含车辆对象列表的类
  2. 然后用户创建Vehicles对象,这些Vehicles每个都有一个属性,比如说License Plate Nr。一旦创建,Vehicle对象就会被添加到Garage对象中的列表中
  3. --稍后-- 用户可以指定给定的Vehicle对象实际上是Car对象或Truck对象(从而可以访问某些特定属性,例如Car的座位数或卡车的货物重量

乍一看,这可能看起来像一个涉及基类和继承的 OOP 教科书问题,但问题更微妙,因为在对象创建时(直到用户决定提供更多信息),计算机不知道确切的车辆类型。

因此我的问题是:你将如何着手实施这个程序流程?OOP是要走的路吗?

只是为了给出一个初步的答案,这就是我到现在为止的想法。只有一个 Vehicle 类,各种属性/值由主程序(而不是类)通过字典处理。但是,我很确定必须有一个更优雅的解决方案(我正在使用 VB.net 进行开发):

Public Class Garage
    Public GarageAdress As String
    Private _ListGarageVehicles As New List(Of Vehicles)

    Public Sub AddVehicle(Vehicle As Vehicles)
        _ListGarageVehicles.Add(Vehicle)
    End Sub
End Class

Public Class Vehicles
    Public LicensePlateNumber As String
    Public Enum VehicleTypes
        Generic = 0
        Car = 1
        Truck = 2
    End Enum
    Public VehicleType As VehicleTypes
    Public DictVehicleProperties As New Dictionary(Of String, String)
End Class

请注意,在上面的示例中,public/private 修饰符不一定反映原始代码

4

5 回答 5

1

让我们首先区分人们可以对车库中的对象(其属性)提出的一组答案与对这些问题的一组答案(其状态)。

如果您只是查看答案集发生变化的场景,则适用简单的状态模式。属性保持不变,状态发生变化。所有对象实例化都是具有常量属性的单一类型。

如果您正在查看更复杂的情况,即车库中对象的可用属性发生变化,则可以使用装饰器模式。但是,我认为这也不太适合您的情况。装饰器模式适用于属性数量可控制的场景,但可能的组合数量可能呈指数增长,因为没有限制哪个与哪个搭配。

我认为最能处理您的情况的场景是对象实际上是未定义的,直到被识别,最初只创建了一个代理(由车辆钥匙表示),一旦对象被完全识别,它似乎一次发生,它是完整的对象被实例化。

您可能希望装饰器位于代理之上,但这也可能不是必需的。

于 2013-06-11T00:18:59.097 回答
1

--稍后 --,用户可以指定给定的 Vehicle 对象实际上是 Car 对象或 Truck 对象

您危险地接近要求将无限制的向下转换作为一项功能。这在托管代码中是不可能的,CLR 提供了绝对不会发生非法向下转换的硬性保证。当您尝试时,它会引发 InvalidCastException。

更具体一点,如果原始对象是作为车辆创建的,那么您将无法解释或访问该对象,就好像它是卡车一样。例如,卡车具有 Vehicle 所没有的 Cargo 属性。事实上,Vehicle 甚至没有 Cargo 的存储空间。将车辆重新解释为卡车将为 Cargo 赋予完整的垃圾价值。更糟糕的是,写入 Cargo 属性会破坏内存。

在某些语言(如 C 和 C++)中可以进行无限制的向下转换。特别是在 C 中,这几乎是不可避免的,void* 是 C 的“对象类”。但是这些语言也以编写在运行时崩溃的代码而闻名。非法的垂头丧气是诱发这种崩溃的一种极好的和常见的方法。这导致的堆损坏非常难以诊断,直到很久以后才发生崩溃,与原始损坏发生的地方相去甚远。

您使用标准工厂模式来创建具有所需属性集的特定类的实例。向上转换到基类总是有效的。例如,这样的工厂将返回 Vehicle 类型的引用,即使它创建了 Truck 对象。稍后将其向下转换为卡车将是有效的。

于 2013-06-11T01:50:37.107 回答
1

当您尝试对现实对象进行建模时,面向对象编程的效果最好,而不是对做没有意义的事情的“神奇”对象进行建模。

在现实世界中,您不能拥有一辆模糊的汽车,但突然变成了皮卡车。因此,以这种方式建模您的系统毫无意义,并且您将遇到各种问题,导致您一次又一次地回到“魔术”。

可以将编译器和运行时环境视为一种“袖珍宇宙”,并且可以将编译器强制执行的某些规则视为适用于该宇宙的“物理定律”。在某些情况下,你会扭曲这些定律,给予一定的补偿,但一般来说,你不应该尝试这样做,因为它会在时空连续体中造成巨大的裂痕(即,你可能会破坏程序的内部状态)。

相反,我会以这种方式建模。你可以有一个“车牌”对象的列表,当你想“创建”一辆皮卡车时,你可以使用一个工厂类,传入车牌对象,它将创建一个使用该许可证对象的皮卡车。

请记住,对象通常包含其他对象。车牌本身就是一个物体,为什么不这样对待它呢?由于您似乎在模棱两可的“车辆”和车牌之间没有真正的联系,所以这更有意义。

于 2013-06-11T02:04:01.140 回答
1

我的理解是:您正在尝试在 VB.net 中实现您可以在 JavaScript 及其构造函数中实际动态执行的操作...

我不知道您是否可以在 VB.net 中动态创建方法、函数、事件或属性,例如:

Public Module SampleMembers

    Public _PaxNum As Integer = 0
    Public _CargoAmount As Integer = 0

    Public Function GetPassengerNumbers() As Integer
        Return _PaxNum
    End Function

    Public Function GetCargoAmount() As Integer
        Return _CargoAmount
    End Function
End Module

然后,在您的应用程序中声明一个基本对象,例如:

Dim MyVehicle As Object

稍后,在运行时,动态地将成员添加到您的车辆对象,例如:

Public Sub ConvertBaseVehicleToCar(ByRef CurrentVehicle As Object)
    ' ...
    Object.AddMember(SampleMember._PaxNum, CurrentVehicle)
    Object.AddMember(SampleMember.GetPassengerNumber(), CurrentVehicle)
    ' Where Object would have a magical Constructor Modyfier...
    ' That would be GREAT... of course
End Sub

但是如果我没记错的话,你不能在 VB.net 中这样做

如果只是关于数据...

我会使用:

Public Class Vehicle
    Private _PropertiesList As New SortedList(Of String, String)

    Public Function AddProperty(ByVal PropertyName As String, ByVal PropertyValue As String) As Boolean
        If _PropertiesList.ContainsKey(PropertyName) Then
            _PropertiesList.Item(PropertyName) = PropertyValue
            Return False ' Property replaced !
        Else
            _PropertiesList.Add(PropertyName, PropertyValue)
            Return Property ' New Property added !
        End If
    End Function

    Public Function RemoveProperty(ByVal PropertyName) As Boolean
        If _PropertiesList.ContainsKey(PropertyName) Then
            _PropertiesList.Remove(PropertyName)
            Return True ' Property actually removed !
        Else
            Return False ' No property with that name !
        End If
    End Function

    Public Function GetPropertiesList() As List(Of String)
        Dim NewList As New List(Of String)
        Dim CurrentProperty As String

        For Each CurrentProperty In _PropertiesList.Keys
            NewList.Add(CurrentProperty)
        Next

        Return NewList
    End Function

    Public Function GetProperty(ByVal PropertyName As String) As String
        If _PropertiesList.ContainsKey(PropertyName) Then
            Return _PropertiesList.Item(PropertyName)
        Else
            Return ""
            ' Or whatever explicit code of your choice
            ' like Return "N/A" or Return "#"
        End If
    End Function
    ' I would replace this latest function by 
    Public Property Item(ByVal PropertyName As String) As String
        ' ...
    End Property

    ' ...

    ' And the Constructor
    Public Sub New(ByVal VehicleType As String)
        InitializeType(VehicleType)
    End Sub

    ' With its default Properties like :
    Private Sub InitializeType(ByVal ProposedType As String)
        ProposedType = ProposedType.Trim().ToUpper()
        Select Case ProposedType
            Case "CAR":
                Item("Type") = "CAR"
            Case "TRUCK":
                Item("Type") = "TRUCK"
            Case "MINIVAN":
                Item("Type") = "MINIVAN"
        End Select
    End Sub

    ' And add a FINAL ReadOnly Property
    Public ReadOnly Property VehicleType() As String
        Get
            Return Item("Type")
        End Get
    End Property
End Class

现在,MyVehicle 可以是任何东西,汽车、卡车、飞机,甚至是 PlanetEarth……

尽管如此,我还是不能在运行时屏蔽或添加方法、函数、属性。我的属性都是“字符串”类型

MyCar.Item("NumberOfWheels") = "6"
' ^^ I'll have to cast this to Integer before using it...
MessageBox.Show(SumOfWheels(MyListOfVehicles).ToString())
' Where :
Public Function SumOfWheels(ByVal ListOfVehicles As List(Of Vehicles)) As Integer
    Dim CurrentVehicle As Vehicle
    Dim CurrentWheels As Integer
    Dim TotalWheels As Integer = 0
    For Each CurrentVehicle In ListOfVehicles
        If Integer.TryParse(CurrentVehicle.Item("NumberOfWheels"), CurrentWheels)
            TotalWheels = TotalWheels + CurrentWheels
        End If
    Next
    Return TotalWheels
End Function

但是,我可以添加一种虚拟类型修饰符:初始 ReadOnly Property VehicleType()

' ...
    Public Property VehicleType() As String
        ' The Getter is the same, but the setter is a litte bit different :
        Set(ByVal NewType As String)
            InitializeType(NewType) ' Simple ? No ! I'll have to edit the Method...
        End Set
    End Property

    Private Sub InitializeType(ByVal ProposedType As String)
        ProposedType = ProposedType.Trim().ToUpper()
        Select Case ProposedType
            Case "CAR":
                Item("Type") = "CAR"
                RemoveProperty("CargoHold")
                Item("Drivers") = "1"
            Case "TRUCK":
                Item("Type") = "TRUCK"
                RemoveProperty("PaxSeats") ' Well, you actually can have one.. or two..
                Item("Drivers") = "1"
            Case "MINIVAN":
                Item("Type") = "MINIVAN"
                Item("Drivers") = "1"
            Case "MOTORBIKE":
                Item("Type") = "MOTORBIKE"
                RemoveProperty("CargoHold")
                Item("Drivers") = "1"
                Item("PaxSeats") = "1"
                Item("NumberOfWheels") = "2"
            Case "JETLINER":
                Item("Type") = "JETLINER"
                Item("Drivers") = "2"
            Case "VINTAGEJETLINER":
                Item("Type") = "VINTAGEJETLINER"
                Item("Drivers") = "3"
        End Select
    End Sub
    ' ...

无论如何,我将不得不在我的车库中使用几辆汽车为特定的例程编写代码。这将是我车库班的成员。每次我想为一组给定的车辆做特定的事情时,我都必须检查它是什么类型的车辆并选择正确的代码路径来运行......

如果您想拥有一堆子模型的车辆,那将变得非常棘手...

' VEHICLE>MINIVAN
' VEHICLE>MINIVAN>CITROEN
' VEHICLE>MINIVAN>CITROEN>3CV
' VEHICLE>MINIVAN>CITROEN>3CV>BASIC
' VEHICLE>MINIVAN>CITROEN>3CV>COLLECTOR
' VEHICLE>MINIVAN>CITROEN>3CV>DEADHULK

但至少,你可以有一个有用的函数来检索在你的车库中具有特定属性的所有车辆:

Public Function GetVehicleUsingProperty(ByVal PropertyName As String, ByVal PropertyValue As String) As List(Of Vehicle)
' And a better one :
Public Function GetVehicleUsingProperty(ByVal PropertiesParam As SortedList(Of String, String)) As List(Of Vehicle)
' ... :P 

就像我看待事物的方式一样。希望其他人可以提供更好的方法来实现所有这些?

于 2013-06-14T02:20:06.380 回答
0

我倾向于拥有一个“车辆”基类,它可以使用您知道的基本属性正常创建(不是抽象的)。包括您定义的 VehicleType 默认设置为“通用”。

为每个子类型创建每个特定类型。以严格的格式定义正确的属性以强制执行良好的代码。

在基本类型中创建一个函数以将车辆属性克隆到传入的对象中。例如。

Public sub CloneTo(byval OtherVehicle as Vehicle)

当需要更具体的“通用”车辆时,创建新的子类型,将其传递给例程以克隆现有信息,并用车库集合中的新类型替换旧类型。

您需要评估车库集合中每个项目的子类型以确定可用的扩展属性,但我认为如果所有正确的级别都到位(较低的级别将最常访问,并且如果任何可以放置的属性始终位于树中的最高级别)例如。车辆 - 汽车 - 轿车。例如,PassengerCapacity 实际上是 Vehicle 的属性。

于 2013-06-11T01:33:24.603 回答