【问题标题】:OOP concept: is it possible to update the class of an instantiated object?OOP 概念:是否可以更新实例化对象的类?
【发布时间】:2013-06-06 16:22:22
【问题描述】:

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

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

乍一看,这可能看起来像是一个涉及基类和继承的 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 修饰符不一定反映原始代码

【问题讨论】:

  • 这更适合programmers.stackexchange,这里它没有建设性
  • 有趣的问题 :)
  • @TimSchmelter 我应该在那里重新发布我的问题吗?或者你可以转让吗?
  • @Federico:你必须在那里重新发布它,没有选项可以关闭投票作为“离题”并建议程序员。
  • 你想描述关于车库中车辆更换的知识状态,这可以通过使用状态设计模式来完成。

标签: vb.net oop object inheritance instantiation


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

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

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

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

      【讨论】:

        【解决方案4】:

        我的理解是:您正在尝试在 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 
        

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

        【讨论】:

          【解决方案5】:

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

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

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

          Public sub CloneTo(byval OtherVehicle as Vehicle)
          

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-06-10
            • 1970-01-01
            相关资源
            最近更新 更多