【问题标题】:How to prevent calling constructor of one class while having access to its member functions in C#?如何在 C# 中访问其成员函数时防止调用一个类的构造函数?
【发布时间】:2017-02-01 16:21:21
【问题描述】:

我有一个班级A,还有一个班级AStore。我的要求是防止所有其他方法初始化类A 的实例,它们应该从AStore 获取实例。另外,我还需要从实例中访问A的成员函数。

工厂模式不适合这个问题,因为A 的构造函数仍然是公开的。理想情况下,当调用类 A 的构造函数同时可以访问其成员函数时,它应该会引发编译错误。

我可以获得 C# 解决方案吗?

【问题讨论】:

  • 我认为您唯一的选择是将A 的构造函数设为私有并在AStore 中使用反射来创建A 的实例。
  • 你可以这样做,但不是类AStore,而是在A上放置一个公共静态CreateInstance方法和一个私有构造函数
  • "工厂模式不适合这个问题,因为 A 的构造函数仍然是公共的" 为什么它仍然是公共的?
  • @JonHanna -- 我认为是public 还是internal 并不重要,关键是他/她不希望程序集中的其他类创建实例(除了AStore).
  • @rory.ap 如果它不是公开的,那么它会立即阻止它,就其他程序集而言,那为什么它仍然是公开的?

标签: c# design-patterns constructor private


【解决方案1】:

为了限制其他人创建类 A 的实例,您可以使用私有构造函数和静态工厂方法来获取该类的实例。

public class A
{
  private A(){}

  public static A GetInstance()
  {
    return new A();
  }

  public void MemberFunctionOfA()
  {
  // blah blah...
  }
}

要强制仅通过 Astore 创建 A 的实例,您可以使用 protected 修饰符并从 A 派生 AStore。这样,只有 AStore 可以访问其受保护的成员,例如“构造函数”或“工厂方法”:

public class Astore : A
{
    public A GetInstanceOfA()
    {
        return base.GetInstance();
    }
}

public class A
{
    protected A() { }

    protected A GetInstance()
    {
        return new A();
    }

    public void MemberFunctionOfA()
    {
        // blah blah...
    }
}

//Usage
public class ConsumerClass
{
    public void Test()
    {
        var a = new A(); // Compile error
        a = new Astore().GetInstanceOfA();
        a.MemberFunctionOfA();
    }
}

但仍有可能另一个类说“UnWantedStore”可以从 A 派生并提供 A 的实例。

【讨论】:

  • 我认为继承方法可能会让维护者感到困惑。例如,如果可以通过 AStore 类访问相同的逻辑,为什么我需要创建一个 A 类?
  • 让我们考虑一个暴露对 A 的依赖的消费者类。根据 OP,A 的实例化应该只允许通过 AStore。并且 Consumer 类将不接受 AStore(Derived instance) 因此可以注入通过 AStore 创建的 A 实例。提议的类是根据 OP 提供的特定要求设计的。它可以根据需要进行多种修改。
  • 在我的评论中,我试图提出一个问题——如果没有 cmets 上的多行,其他开发人员会理解 OP 的意图吗?
  • 好吧,遵循 SOLID 原则的开发人员永远不会遇到这样的具体问题.. :)
  • 确实,我们不能阻止其他人继承类A。感谢您快速而好的建议。
【解决方案2】:

另一种方法是将AStoreA 类移至专用项目,并将A 类的构造函数设为内部。

// Project A
namespace ProjectA
{
    public class A
    {
        public int PropertyOne { get; set; }
        public string PropertyTwo { get; set; }

        internal A() {}            
    }

    public class AStore
    {
        public A CreateA()
        {
            //internal constructor can be used
            return A();
        }
    }
}

// Project ConsumerOfA
namespace ConsumerOfA
{
    public static void UseA()
    {
        var store = new AStore();
        var instanceOfA = store.CreateA();

        // have access to the A's public members
    }
}

通过这种方法,您将获得您想要实现的完美封装。

【讨论】:

  • 这也是我们采用的最后一种方法。感谢您的建议。
【解决方案3】:

抽象类来救援!

确实,还有另一种可能的方法!我从未使用过它,但它可能适用于您的场景。请参阅以下代码示例:

public abstract class A 
{
    public string Text { get; set; }

    public string SayHello() => "hello world!";
}

public class AStore
{
     private class AInternal : A {}

     public void DoStuff()
     {
         A a = new AInternal();
         a.Text = "whatever";
         string helloText = a.SayHello();
     }
}

让我们解释一下方法:

  • A 类是抽象的,因此无法实例化。
  • AStore 类实现了一个名为AInternalprivate 嵌套类,它只继承A,让AStore 成员能够实例化A。由于AInternal 是私有的,所以除了AStore 之外没有其他类可以实例化AInternal
  • AStore可以访问公共A成员,因为AInternal继承A

【讨论】:

    【解决方案4】:

    你也可以通过反射来做到这一点:

    public class ClassA
    {
        // The constructor(s) have to be private
        private ClassA() { }
    
        // Whatever other code you want
    }
    
    public class ClassB
    {
        public static ClassA GetClassAInstance()
        {
            // Use reflection to get the private default constructor
            ConstructorInfo constructor = typeof(ClassA).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
            ClassA instance = constructor.Invoke(new object[] { }) as ClassA;
    
            return instance;
        }
    }
    

    您可以找到有关 GetConstructor 方法here 的更多信息。

    【讨论】:

    • 但是任何人都可以做同样的事情。
    • @OldFart 无论你使用什么解决方案都是如此——使用私有字段和变量并不能真正阻止人们访问它,如果他们真的想要的话。
    • 那我们需要去掉OO特性中的“封装”
    • @Fabio 不一定——只是可以绕过它。
    猜你喜欢
    • 2011-12-07
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    • 2014-10-12
    相关资源
    最近更新 更多