【问题标题】:Help with allowing a class to be customized by other developers帮助允许其他开发人员自定义类
【发布时间】:2011-04-11 05:55:50
【问题描述】:

更新:添加了一些示例代码以帮助澄清。

嗨,感觉这不应该那么复杂,但我想我只是不知道我正在尝试做的事情的正确名称。我正在处理一个 ASP.net 项目。

这个概念很简单: 我有一个提供一些 ecomm 功能的库。 库中的一个类包含有关计算税收的功能。 库中的一个类围绕购物车标题。 此类由 Web 项目使用。

会有一个类似的方法:carthead.SaveCart。 在 SaveCart 中,它需要调用 tax.CalculateTax

我需要做的是想办法让 carthead.SavCart 始终可以访问特定函数,例如 tax.CalculateTax(例如,该函数必须始终对库可用)。

但是,我想允许任何人创建不同版本的 tax.CalculateTax 方法。

我尝试使用可覆盖的基类做一些事情,但我发现即使在 Web 项目中覆盖税基类,它也只会在我调用时调用税类的覆盖版本网络项目。 我不能将税收类变成一个接口,因为当我这样做时,我不能将 tax.CalculateTax 的结果定义为一个列表(因为在这种情况下 t 是一个接口,而不是一个实际的类) - 这个列表必须由 carthead.SaveCart 方法使用。

因此,当您单步执行代码时,您会发现当 Web 项目调用 carthead.SaveCart 方法时,carthead.SaveCart 方法无法从 Web 项目中访问被覆盖的代码...并导致调用 tax.CalculateTax 方法的非覆盖版本。

我确定我遗漏了一些东西,但我什至不确定我应该研究什么,或者我想要完成的事情的正确名称是什么。

归根结底,我需要能够重写一个方法,并从库中的不可重写方法调用该方法的重写版本。

谁能告诉我我搞砸了什么或者我应该看什么?

更新: 添加了一些代码 sn-ps 以帮助澄清:

这是有问题的 CartHeader 类的一部分:

Public Class CartHeader

'其他类的东西省略了

Public Function UpdateCartStep2(ByVal CartNo As Long, ByVal Username As String, _
ByVal PmtMethod As String, ByVal ShipMethod As String, _
ByVal ShipComplete As Boolean, ByVal ShipCost As Double, _
ByVal ShipInstr As String, Optional ByVal TaxGroup As String = "", _
Optional ByVal PickupLoc As String = "", _
Optional ByVal FuelSurcharge As Double = 0, _
Optional ByVal Misc As String = "", _
    Optional ByVal TaxThisSomeTaxOrder As Boolean = False, _
    Optional ByVal ShipToID As Long = 0, _
    Optional ByVal ShipToZip As String = "", _
    Optional ByVal mCustCode As String = "", _
    Optional ByVal CustTax As Tax = Nothing) As Integer
    '=================>
    'note that the last parameter is new which is what we're currently using to pass in the customtax class so that we can consume it inside this method
    '==================>


    If IsNothing(CustTax) Then
        CustTax = New Tax
    End If

    '6-29-08 this stored proc was updated to allow for fuel surcharge
    'added fuel surcharge parameter

    Dim Resultval As Integer
    Dim strConnect As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
    Dim SqlCommand As New SqlCommand("sp_UpdateCartStep2", strConnect)

    Try

        Dim SubTotalAmt As Double
        SubTotalAmt = GetCartSubTotal(CartNo)

        GetCartHeader(CartNo)

        Dim CartTax As Double

        Dim SystemTypeID As Integer = CInt(ConfigurationManager.AppSettings("SystemTypeID").ToString)

        Select Case SystemTypeID
            Case 1

                If profile.AllowTerms = False Then
                    CartTax = CalcTax(SubTotalAmt, ShipCost, FuelSurcharge, m_Ship_State_Province)
                Else
                    CartTax = 0
                End If
            Case 2
                '6-29-08 added to figure fuel surcharge
                'Dim CustTax As New Tax
                'Dim CustCode As String = System.Web.HttpContext.Current.Profile("CustCode")
                Dim lCustTax As New List(Of Tax)

                '=========================>
                'note that this part of the header must always call into the calctax method.
                'it should be able to call the custom method if it has been defined.
                lCustTax = CustTax.wa_cc_CalcTax(mCustCode, ShipToID, SubTotalAmt, ShipCost, FuelSurcharge, CStr(m_Ship_State_Province), CStr(ShipToZip))
                '==========================>


                For Each ct As Tax In lCustTax
                    CartTax += ct.Tax
                Next
                'CartTax = CalcTax(SubTotalAmt, ShipCost, FuelSurcharge, m_Ship_State_Province, TaxGroup)

        End Select

        SqlCommand.CommandType = CommandType.StoredProcedure
        strConnect.Open()

        SqlCommand.Parameters.AddWithValue("@CartNo", SqlDbType.BigInt).Value = CartNo
        SqlCommand.Parameters.AddWithValue("@Username", SqlDbType.VarChar).Value = Username
        SqlCommand.Parameters.AddWithValue("@PmtMethod", SqlDbType.VarChar).Value = PmtMethod
        SqlCommand.Parameters.AddWithValue("@ShipMethod", SqlDbType.VarChar).Value = ShipMethod
        SqlCommand.Parameters.AddWithValue("@ShipCompleteFlag", SqlDbType.Bit).Value = ShipComplete
        SqlCommand.Parameters.AddWithValue("@ShipCost", SqlDbType.Money).Value = ShipCost
        SqlCommand.Parameters.AddWithValue("@Tax", SqlDbType.Money).Value = CartTax
        SqlCommand.Parameters.AddWithValue("@ShipInstr", SqlDbType.VarChar).Value = ShipInstr
        SqlCommand.Parameters.AddWithValue("@PickupLoc", SqlDbType.VarChar).Value = PickupLoc
        SqlCommand.Parameters.AddWithValue("@FuelSurcharge", SqlDbType.Float).Value = FuelSurcharge

        '1-30-08 Changed to accomodate holding the carrier number when selecting collect freight
        'required modification of the sp_UpdateCartStep2 stored procedure.
        SqlCommand.Parameters.AddWithValue("@Misc3", SqlDbType.VarChar).Value = Misc3

        SqlCommand.ExecuteNonQuery()
        Resultval = 0
    Catch ex As Exception
        Resultval = -1
        System.Web.HttpContext.Current.Trace.Write(ex.Message)
    Finally
        strConnect.Close()
    End Try

    Return Resultval
End Function

结束类


这是我们用作基类的类...如果基函数 calcs 不适用,它会覆盖 wa_cc_calctax。

Public Class Tax


Private _Tax As Double
Public Property Tax() As Double
    Get
        Return _Tax
    End Get
    Set(ByVal value As Double)
        _Tax = value
    End Set
End Property

Private _TaxRate As Double
Public Property TaxRate() As Double
    Get
        Return _TaxRate
    End Get
    Set(ByVal value As Double)
        _TaxRate = value
    End Set
End Property


Private _TaxDesc As String
Public Property TaxDesc() As String
    Get
        Return _TaxDesc
    End Get
    Set(ByVal value As String)
        _TaxDesc = value
    End Set
End Property


Private _TaxGroupID As String
Public Property TaxGroupID() As String
    Get
        Return _TaxGroupID
    End Get
    Set(ByVal value As String)
        _TaxGroupID = value
    End Set
End Property


Private _TaxJurisdictionID As String
Public Property TaxJurisdictionID() As String
    Get
        Return _TaxJurisdictionID
    End Get
    Set(ByVal value As String)
        _TaxJurisdictionID = value
    End Set
End Property



Private _TaxCustCode As String
Public Property TaxCustCode() As String
    Get
        Return _TaxCustCode
    End Get
    Set(ByVal value As String)
        _TaxCustCode = value
    End Set
End Property


Private _TaxFreight As Boolean
Public Property taxFreight() As Boolean
    Get
        Return _TaxFreight
    End Get
    Set(ByVal value As Boolean)
        _TaxFreight = value
    End Set
End Property




Public Enum TaxableStatus
    All
    None
    some
End Enum


''' <summary>
''' It will first try to figure out if we're shipping to the same zip as the ship to
''' if it is the same, then we'll use the ship-tos tax group
''' if it is different, then we'll go to manual tax.
''' in manual tax, the customer record is reviewed and the class_1id field is interogated.
''' The code selected tells us what states the customer is taxable for.
''' If we are in those states, then the customer tax group is chosed based on the state.
''' </summary>
''' <param name="mCustCode"></param>
''' <param name="mShipToID"></param>
''' <param name="SubTotalAmt"></param>
''' <param name="FreightCost"></param>
''' <param name="FuelSurcharge"></param>
''' <param name="m_Ship_State_Province"></param>
''' <param name="m_Zip"></param>
''' <param name="TaxGroup"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overridable Function wa_cc_CalcTax(ByVal mCustCode As String, _
                       ByVal mShipToID As String, _
                       ByVal SubTotalAmt As Double, _
                     ByVal FreightCost As Double, _
                    ByVal FuelSurcharge As Double, _
                    ByVal m_Ship_State_Province As String, _
                    ByVal m_Zip As String, _
                     Optional ByVal TaxGroup As String = "") As List(Of Tax)

    'do some 'normal' tax calcs.


    Return New List(Of Tax)

End Function

结束类


这是覆盖 wa_cc_calctax 函数的 CustomTax 类:

Public Class CustomTax

Inherits Tax


''' <summary>
''' It will first try to figure out if we're shipping to the same zip as the ship to
''' if it is the same, then we'll use the ship-tos tax group
''' if it is different, then we'll go to manual tax.
''' in manual tax, the customer record is reviewed and the class_1id field is interogated.
''' The code selected tells us what states the customer is taxable for.
''' If we are in those states, then the customer tax group is chosed based on the state.
''' </summary>
''' <param name="mCustCode"></param>
''' <param name="mShipToID"></param>
''' <param name="SubTotalAmt"></param>
''' <param name="FreightCost"></param>
''' <param name="FuelSurcharge"></param>
''' <param name="m_Ship_State_Province"></param>
''' <param name="m_Zip"></param>
''' <param name="TaxGroup"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function wa_cc_CalcTax(ByVal mCustCode As String, _
                       ByVal mShipToID As String, _
                       ByVal SubTotalAmt As Double, _
                     ByVal FreightCost As Double, _
                    ByVal FuelSurcharge As Double, _
                    ByVal m_Ship_State_Province As String, _
                    ByVal m_Zip As String, _
                     Optional ByVal TaxGroup As String = "") As List(Of Tax)
    Dim lTX As New List(Of Tax)

    Dim mUseP21Tax As Boolean = True
    If mShipToID <= 0 Then
        mUseP21Tax = False
    End If

    If FreightCost <= 0 Then
        FreightCost = 0
    End If

    Dim tx As New CustomTax
    Dim ZipMatch As Boolean
    If mShipToID > 0 Then
        'we're dealing with a selected ship to so we should see if it all still matches
        ZipMatch = CheckZipAgainstShipTo(m_Zip, mCustCode, mShipToID)
    Else
        'this item is not a selected ship-to so no need to look for a match
        ZipMatch = False
    End If

    If ZipMatch = True Then

        lTX = LookupTaxForShipTo(mCustCode, mShipToID, SubTotalAmt, FreightCost)
    Else
        lTX = LookupManualTax(mCustCode, m_Ship_State_Province, SubTotalAmt, FreightCost, , m_Zip)


    End If


    Return lTX

结束函数 结束类


所以问题的部分原因是: 1) 如果我将 Tax 类设为接口或抽象类,那么我必须将其“新建”为 cartheader 类中的新类,以便我可以调用 wa_cc_lookupclass 方法。 2) 当我们在 Cartheader 类中新建一个税类时,我们并没有更新该税类的自定义实例,因此不使用自定义代码。

目标如下: 1) 提供具有“正常”功能的基本税类。 2) 允许用户覆盖 Web 应用的 app_code 文件夹中的税类,以创建新的逻辑来计算税款。它仍将具有与原始签名相同的输入和输出签名。 3) cartheader.UpdateCartStep2 方法必须能够从基类或重写的自定义类(如果它被重写)访问 wa_cc_calctax 函数。

在这个示例中,我们显然已经破解了它并将自定义版本的税类 (CustomTax) 作为参数传递到 UpdateCartStep2 方法中。这是我们根据此处的建议实施的解决方法……但是我们知道这不是“正确”的方法。

【问题讨论】:

  • 使用类似 base.SaveCart() 的东西?

标签: vb.net class interface methods overriding


【解决方案1】:

我能想到两个选择。

  1. 将您的购物车类设为抽象类,并将 CalculateTax 方法设为抽象方法。这将迫使其他开发人员实现自己的 CalculateTax 方法并扩展您的基类。

  2. 将参数传递给 SaveCart 方法(或类构造函数),以提供有关如何计算税款的信息,例如委托或函数。然后 SaveCart 方法调用该委托来执行“CalculateTax”部分。

【讨论】:

  • 在短期内,我使用了 #2 变体...实际上是在自定义类的实例中传回...这当然不是“正确”的做法它,但它确实得到了我现在需要的结果。
  • 您可能能够传递 Func 参数,而不是整个实例。 Func(一个函数)定义了函数的输入和输出。它是一个代表,将完全满足您的需求。 msdn.microsoft.com/en-us/library/bb549151.aspx
  • 我最终可能会选择 Func 选项,但似乎应该有更清洁的方式。
  • 我很好奇这样的例子,到底谁会打电话寻求帮助?如果您有一位专家或其他方面的朋友,那就太好了,但我愿意花钱请人一对一地看这个并帮助我找出最佳方向。那么,你打电话给微软还是当地的科技学校?有什么想法吗?
  • 您来到 StackOverflow 或类似网站。或者,如果您想付款,请聘请顾问来帮忙!
【解决方案2】:

类似于以下内容:

abstract class BaseCartOperations
{
    public void SaveCart ()
    {

        // ...

        CalculateTax();
    }


    protected void CalculateTax ()
    {
        // Base Tax Stuff
        CalculateTaxInternal();
    }


    // Force implementation
    protected abstract void CalculateTaxInternal ();
}

? (或其一些变体)。

【讨论】:

  • 我已经尝试过,但我想我会重新审视它。
  • 抽象类的挑战在于这实际上是2个类。 CartHead 是一个类,而 Tax 是一个类。如果 tax 是抽象类,则 CartHead 无法实例化 tax 的实例。如果代码没有被覆盖,Carthead 需要访问基类。如果我让 Carthead 类创建一个新的 customtax 实例(从 tax 继承),那么我就回到了原来的位置。当 carthead 调用 customtax 类时,它不会看到它已在 Web 项目级别被覆盖,而只会使用在 carthead 类中新建的 customtax 类。
  • 这并不重要,但在这种情况下,calctax 会返回一个 列表。税类包含辖区名称、百分比、税费、税额等详细信息。
【解决方案3】:

重新阅读问题和您的 cmets 后,我看到您想要解决 2 件事:

  1. 如何强制调用基类方法
  2. 如何让第三方提供的类参与系统

下面是对上述问题的进一步回答,但我更喜欢:

定义一个ITaxCalculator(你可以找到一个更好的名字......)。在需要进行计算的方法中接收 ItaxCalculator。提供 DefaultTaxCalculator 或类似的东西(如果适用),或者让客户端调用传递给您,或者如果计算器参数为空,则将其用作默认值。

如果您的代码库难以将其传递到流程中,请定义一个 TaxCalculatorFactory(如下面的 CartFactory),并且仅执行 TaxCalculatorFactory.Create() / 如果默认情况下适用,则应将其设置为 DefaultTaxCalculator。

一旦您有了 ItaxCalculator 的实例,您就可以调用 taxCalculator.CartTax(currentCart) 来获取购物车的税金。

这种方式扩展点更加集中,恕我直言,最终使其更简单。


回答原问题:

对于 1,不能直接强制。它的子类决定调用 base.TheSameMethod(),这允许它在执行基本方法之前和/或之后放置一些逻辑。

您可以通过在覆盖的方法中不包含您希望始终调用的逻辑来解决此问题。您所做的是调用 1 或 2 个可以被覆盖的方法,对于 CartSave,您可以定义 OnCartSaving 和 OnCartSaved。 CartSave 不能被覆盖,但是当它调用那些可以被覆盖的 2 时,您正在提供扩展点。

对于 2,我认为这是一个全新的问题,有多种解决方法。

如果您需要控制创建实例的时间,您可以创建一个工厂,它可以由客户端代码(或通过应用程序的 .config)配置以使用它们的类。就像框架为不同的数据提供者所做的那样。

一个简单的变体可能是使用 Func 公开静态属性,客户端代码可以将其设置为如下函数:

CartFactory.Create = () => new MySpecificCart();

如果它是 asp.net,您可以将它放在应用程序的 global.asax 开头。然后,您可以在需要创建购物车实例的地方: CartFactory.Create();


原始答案 - 忽略/我一开始没有得到问题:

只是猜测,但您似乎没有使用 override 关键字在特定类中声明方法。

如果发生上述情况,您可能会收到来自编译器的警告。

【讨论】:

  • 不,确认“自定义”版本的 tax 肯定使用了覆盖...但是在使用该库的 Web 项目中声明了这一点。此时该库将被编译,因此最终用户将无法直接在类中进入该类。
  • @haldrich98 是的,我刚刚重新阅读了您的问题,但我错过了手头的问题是您想强制调用 base 的方法。你只需要改变方法,也就是说我刚刚在你的 cmets 中读到它本身并不能解决你的问题/因为你想知道如何使用覆盖的版本来处理它。
【解决方案4】:

在与 Microsoft 讨论(并收取费用)后,他们确定在这种情况下唯一可以做的就是将新对象的实例或谓词函数传递给类。这是因为类被编译成dll,而在内部,dll会引用dll内部定义的基类... 因此,我们创建了一个选项来传递基类的重写实例......让代码检查传入的实例是否为空白,以及它是否是基类的新实例。 如果提供了它,那么我们使用传入的实例而不是新建一个新的基类。

希望这对其他人有所帮助。

【讨论】:

    猜你喜欢
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 2011-04-09
    相关资源
    最近更新 更多