VB.NET - 滥用实例变量?

3
请看下面的代码:
    Public Class A
    Public person1 As Person
End Class

Public Class B
    Inherits A

    Public Function CheckGender() As Boolean
        If person1._Gender = "M" Then
            CheckGender = True
        End If
    End Function

End Class

Public Class C
    Inherits B
    Public Function CheckAge() As Boolean
        If person1._Age > 30 Then
            CheckAge = True
        End If
    End Function

End Class

Public Class D
    Inherits C

    Public Sub SomeMethod()
        Dim list As List(Of Person) = New List(Of Person)
        Dim p1 As New Person("Ian", "M", 31)
        Dim p2 As New Person("Susan", "F", 20)
        Dim p3 As New Person("Mark", "M", 22)
        list.Add(p1)
        list.Add(p2)
        list.Add(p3)

        For Each Person As Person In list
            person1 = Person
            If CheckAge() And CheckGender() Then
                'Do something
            End If
        Next
    End Sub

    Public Shared Sub Main()
        Dim d As New D
        d.SomeMethod()
    End Sub

End Class

Public Class Person
    Public _Name As String
    Public _Gender As String
    Public _Age As String
    Public Sub New(ByVal Name As String, ByVal Gender As String, ByVal Age As Integer)
        _Name = Name
        _Gender = Gender
        _Age = Age
    End Sub
End Class

c.SomeMethod循环遍历三个人并进行两个检查:b.CheckGenderc.CheckAgeCheckGenderCheckAge使用超类A的实例变量。

在实际环境中,代码每天循环处理100,000条数据库记录,并删除那些同时满足CheckGenderCheckAge条件的记录。在这种情况下,使用实例变量是一个糟糕的设计选择吗?我一直被教导使用局部变量。我希望在每次循环时将Person对象传递给CheckGenderCheckAge。或者说这真的没有关系吗?

请注意,上面的代码只是一个假设性的例子。在实际应用程序中,CheckGenderCheckAge是复杂的函数。

2个回答

2
只要CheckGenderCheckAge没有访问到类层次结构中的任何私有、受保护或内部成员,并且它们是公共函数,并且它们的逻辑对于任何实例(如ABC)都是相同的,那么将这些方法放在另一个类中是更好的设计。如果可能的话,将它们设为静态函数。您可以让它们接受最一般的类实现(例如A),该实现允许检查年龄或性别。从您的代码中获取,甚至可以传递Person属性,而不使用任何ABC类。
在上述情况下使用继承和这样的逻辑是允许的,只要您需要执行以下任何一个或所有操作:
符合特定接口或基类,ABC类必须实现/扩展该接口或基类,该接口或基类提供CheckGenderCheckAge方法。如果您将对象传递给第三方API,并且该API接受基类/接口作为参数并在内部调用检查方法,则这可能是唯一的解决方案。
以下是C#示例:
public static class CheckUtil
{
    public static bool CheckAgeOfPerson(Person p)
    {
        return p.Age > 30;
    }
    public static bool CheckAgeOfObject(A obj)
    {
        // NOTE: obj.person1 must be accessible - by being either public or internal.
        // If this class is in another assembly, internal is not useful
        return CheckAgeOfPerson(obj.person1);
    }
}

A objA = ...;
B objB = ...;
C objC = ...;

CheckUtil.CheckAgeOfObject(objA);
CheckUtil.CheckAgeOfObject(objB);
CheckUtil.CheckAgeOfObject(objC);

CheckUtil.CheckAgeOfPerson(objA.person1);
CheckUtil.CheckAgeOfPerson(objB.person1);
CheckUtil.CheckAgeOfPerson(objC.person1);
  • 为类提供具体的实现。如果你需要在 CheckAge 中针对 A 的实例执行某些逻辑,但是针对 B 的实例进行完全不同的验证,或者在 C 中结合现有逻辑和一些新逻辑,则继承是你的朋友。但是,如果是这种情况,我更喜欢将 CheckGenderCheckAge 暴露给接口,并通过接口调用它们。这样,继承是有效的,但不是强制性的,只要满足接口。

以下是 C# 的示例:

public interface IGenderCheckable
{
    bool CheckGender();
}

public interface IAgeCheckable
{
    bool CheckAge();
}

public class A : IAgeCheckable, IGenderCheckable
{
    public virtual bool CheckGender()
    {
        return this.person1.Gender.Equals("M");
    }

    public virtual bool CheckAge()
    {
        return this.person1.Age > 30;
    }
}

public class B : A
{
     public override bool CheckAge()
     {
          // combine custom logic with new logic
          return this.person1.Age < 0 || base.CheckAge();
     }
}

对于复杂情况,也可以使用两种方法的组合(当然要比年龄和性别检查复杂得多):

public class A : IAgeCheckable, IGenderCheckable
{
    ...
}

public static class CheckUtil
{
    public static bool CheckAge(IAgeCheckable obj)
    {
        return obj.CheckAge();
    }
    public static bool CheckGender(IGenderCheckable)
    {
        return obj.CheckGender();
    }
}

关于实例变量和局部变量的使用 - 在 .NET 中使用实例变量,特别是当它们是值类型时,会有性能上的缺陷。例如,使用本地成员 int _someIntMember 会被转换为 this._someIntMember - 这将调用堆来获取 this 对象,然后访问其 _someIntMember 成员。而将成员作为局部变量,则将其值放入堆栈中,并从那里读取,无需不必要地经过堆。此外,堆栈比堆更快。
然而,我不能说过多地使用堆是对其的滥用,也不能说过多地使用局部变量是误用。这取决于所需的性能和代码的复杂性。有时,局部变量使代码更易读,但如果太多,您可能会很容易地忘记每个变量的含义(这可能比疏忽的性能提升更严重)。因此,这是一个风格和必要性的问题。

你是这个问题的唯一回答者。我不明白你上次的评论。抱歉。 - w0051977
抱歉,我不知道你的意思。你认为这是不良习惯吗?再次感谢。 - w0051977
这与性能相关,但绝对不是差的。使用太多本地变量的缺点是潜在的混淆,因为小幅度性能提升可能是可读性和可维护性良好的代码的次要好处。 - Ivaylo Slavov
那么实例变量的优点是更清晰的代码,缺点是稍微降低了性能(在您看来)?这是最后一个问题。 - w0051977
一般来说,是的。实例成员更容易理解其代表的含义,并与主对象耦合,因此在逻辑上更易读。如果您的唯一目标是性能最大化,请在确保代码成熟且不会进一步更改时,在其他改进的最后阶段使用本地变量。当然,如果问题只涉及少量本地变量(例如collection.Count属性等),那么使用它们是可以的。 - Ivaylo Slavov
显示剩余10条评论

2

如果您有兴趣“修复”您的代码,将Person变为属性而不是字段,请按照以下方式更改Class A的实现:

Public Class A
    Public Property Person1 As Person

   Public Overridable Function ComputeAge() As Integer
       Return Person1.Age
   End Function

End Class

这里的好处是,如果需要,您可以在以后添加其他抽象层来获取和设置此属性。编译器将为自动属性生成一个隐藏的私有后备字段。您仍然可以从任何实现类中访问Person1:

Public Class B
   Inherits A

   Public Overrides Function ComputeAge() As Integer
       Return MyBase.Person1.Age.AddYears(1)
   End Function
End Class

+1. 属性既有好处也有坏处。好处是,它们通常会作为优化的一部分被编译器内联,并自动创建局部变量。坏处是:如果该属性不是您的代码,而且您不确定它实际上做了什么,那么甚至属性的 getter 在其实现中执行意外逻辑可能会让您想知道瓶颈在哪里(例如 NHibernate 实体代理的延迟属性)。只要这不是问题,也没有风险,这是一个好习惯。 - Ivaylo Slavov
@Jim Wooley,谢谢。您认为person应该是A的实例变量还是应该作为参数传递给CheckAge和CheckGender。或者这真的无关紧要吗?+1。 - w0051977

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