【问题标题】:C# restricting use of a classC# 限制类的使用
【发布时间】:2019-06-20 12:51:59
【问题描述】:

下面的代码做了我想做的事。 Main 方法中的代码看起来和行为完全符合预期。但是,如果 UserMenu、Home 和 DropdownMenu2 类只能由 HeaderNavigationMenu 使用,以防止其他开发人员试图在 HeaderNavigationMenu 类之外使用它们,那将是更可取的。此外,大多数文章都不愿公开所有内容。

问题: 使用的设计模式是否合适,或者在这种情况下是否有更好、更容易接受的设计模式?


编辑:这种设计的原因。

  1. 我希望 HeaderNavigationMenu 的最终用户能够使用点符号来获取可用选项的列表。该架构实现了这一目标(例如:navigationMenu.DropdownMenu2.SelectOption3())
  2. 希望其他最终可能需要编辑代码的人了解 UserMenu、Home 和 DropDownMenu2 类非常专门设计为由 HeaderNavigationMenu 类实现。

public class HeaderNavigationMenu
{
    public HeaderNavigationMenu()
    {
        UsersMenu = new UsersMenu();
        Home = new Home();
        DropdownMenu2 = new DropdownMenu2();
    }

    public UsersMenu UsersMenu { get; set; }
    public Home Home { get; set; }
    public DropdownMenu2 DropdownMenu2 { get; set; }
}

public class UsersMenu
{
  ...
}

public class Home
{
  ...
}

public class DropdownMenu2
{
  public void SelectOption3()
  {
    ...
  }
  ...
}

static void Main(string[] args)
{
  HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
  navigationMenu.DropdownMenu2.SelectOption3();

  // The following code is an example of undesired capability; 
  // prefer if Home class could only be 
  // used by HeaderNavigationMenu class
  Home home = new Home();
}

【问题讨论】:

  • 你说不是所有的事情都应该公开,但一切都是公开的,所以我不明白你的问题。实现这一点的唯一方法是如果它们是私有子类,那会很奇怪。不要试图阻止这种情况,请使用更合适的名称以显示意图
  • @CamiloTerevinto 不会把它们变成私有子类破坏代码吗? OP 在 HeaderNavigationMenu 类中公开公开这些类型的属性,因此他们将无法保持这些公开并限制类型,对吧?
  • @CodeStranger 这可能会发生,是的,我没有测试过。这更像是一般性评论
  • 限制类的使用是什么意思?限制哪些用途——创建实例、某些方法或属性,甚至知道类的存在?将其限制为什么 - 同一程序集中的其他类型,或 HeaderNavigationMenu 之外的其他类型?质疑我们暴露了多少以及为什么暴露是件好事,但我们仍然必须有一个理由。我不会这样做,因为普遍反对公开课程。而是想,“如果其他东西可以调用这个方法或创建这个类的实例,会发生什么坏事?”
  • 感谢您的所有意见。是的,代码中的所有内容都是公开的,我遇到过很多帖子,这些帖子对于公开所有内容非常重要。我想要一些关于最佳实践的指导。你在上面和下面都提出了一些非常好的观点,关于表达意图而不是仅仅因为对这种方法有普遍的负面情绪而不做某事。只是想要一些见解。非常感谢您的想法和智慧。

标签: c# design-patterns architecture


【解决方案1】:

限制对类构造函数的访问。如果它们被声明为“内部”,那么这些类只能由您的代码创建。

【讨论】:

  • 如果代码在库中,这很好。应该也应该让这些类sealed,所以人们不能通过创建自己的派生类来绕过限制
  • 是的,我希望能够指定允许哪些类使用这个特定的类(限制我的代码中哪些类可以使用 UserMenu、Home 和 DropdownMenu2。
【解决方案2】:

如果您希望防止 UsersMenuDropdownMenu2Home 从外部 HeaderNavigationMenu 实例化,但仍与 HeaderNavigationMenu 在同一个项目中,那么有一个巧妙的技巧可以实现这种行为。您可以将公共嵌套类与私有构造函数一起使用,这些构造函数静态初始化它们自己的工厂方法。基本模板是:

public class Outer{
  private static Func<Inner> _innerFactory;
  public Inner ExposedInner {get; private set;}

  public Outer(){
    // Force the static initializer to run.
    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Inner).TypeHandle);

    // Call the newly created factory method instead of a regular constructor.
    ExposedInner = _innerFactory();
  }

  public class Inner {
    static Inner(){
      // Initialize Outer's static factory method.
      _innerFactory = () => new Inner();
    }

    // Inner cannot be instantiated (without reflection) because its constructor is private.
    private Inner(){}

    // This method is now exposed for anyone to use.
    public void DoStuff(){ Console.WriteLine("Did stuff"); }
  }
}

这是您的示例中实现的这个概念:

class Program
{
    static void Main(string[] args)
    {
        HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
        navigationMenu.DropdownMenu2.SelectOption3();

        // This line will no longer work because the constructors
        // for the inner classes are private.
        HeaderNavigationMenu.HomeImpl home = new HeaderNavigationMenu.HomeImpl();

        Console.ReadKey();
    }
}

public class HeaderNavigationMenu
{
    //Private factory methods that are statically initialized
    private static Func<UsersMenuImpl> _createUsers;
    private static Func<DropdownMenu2Impl> _createDropdown;
    private static Func<HomeImpl> _createHome;

    public HeaderNavigationMenu()
    {
        //Force the static constructors to run
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(UsersMenuImpl).TypeHandle);
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(HomeImpl).TypeHandle);
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(DropdownMenu2Impl).TypeHandle);

        UsersMenu = _createUsers();
        Home = _createHome();
        DropdownMenu2 = _createDropdown();
    }

    public UsersMenuImpl UsersMenu { get; set; }
    public HomeImpl Home { get; set; }
    public DropdownMenu2Impl DropdownMenu2 { get; set; }

    public class UsersMenuImpl
    {
        //Static constructor to make the class factory method
        static UsersMenuImpl()
        {
            _createUsers = () => new UsersMenuImpl();
        }

        private UsersMenuImpl() { }
    }

    public class HomeImpl
    {
        //Static constructor to make the class factory method
        static HomeImpl()
        {
            _createHome = () => new HomeImpl();
        }

        private HomeImpl() { }
    }

    public class DropdownMenu2Impl
    {
        //Static constructor to make the class factory method
        static DropdownMenu2Impl()
        {
            _createDropdown = () => new DropdownMenu2Impl();
        }

        private DropdownMenu2Impl() { }

        public void SelectOption3()
        {
        }
    }
}

这样,您仍然可以使用内部类的所有公共属性,但是没有人能够从外部 HeaderNavigationMenu 实例化内部类,并且只有 HeaderNavigationMenu 可以访问工厂方法。

【讨论】:

    【解决方案3】:

    我真的不明白你的用例是什么,而且我从来没有这样编码,但是只公开HeaderNavigationMenu 所需行为的一种方法是使类内部和变量私有,然后只公开SelectOption3() 方法,如下。

    如果您取消注释该行

    //Home home = new Home();
    

    你会得到一个编译器错误。

    class Program
    {
        static void Main(string[] args)
        {
            HeaderNavigationMenu navigationMenu = new HeaderNavigationMenu();
            navigationMenu.DropdownMenu2SelectOption3();
    
            // The following code is an example of undesired capability; 
            // prefer if Home class could only be 
            // used by HeaderNavigationMenu class
            //Home home = new Home();
        }
    }
    public class HeaderNavigationMenu
    {
        UsersMenu usersMenu;
        Home home;
        DropdownMenu2 dropdownMenu2;
    
        public HeaderNavigationMenu()
        {
            usersMenu = new UsersMenu();
            home = new Home();
            dropdownMenu2 = new DropdownMenu2();
        }
    
        public void DropdownMenu2SelectOption3()
        {
            dropdownMenu2.SelectOption3();
        }
    
        class UsersMenu
        {
        }
    
        class Home
        {
        }
    
        class DropdownMenu2
        {
            public void SelectOption3()
            {
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以创建 UsersMenuHomeDropdownMenu2 public abstract 类。然后将 private 类嵌套在 HeaderNavigationMenu 内,这些类扩展了 public abstract 版本。

      public abstract class UsersMenu
      {
      }
      
      public abstract class Home
      {
      }
      
      public abstract class DropdownMenu2
      {
         public void SelectOption3()
         {
            // Code for SelectOption3...
         }
      }
      
      public class HeaderNavigationMenu
      {
          public HeaderNavigationMenu()
          {
              UsersMenu = new UsersMenuImpl();
              Home = new HomeImpl();
              DropdownMenu2 = new DropdownMenu2Impl();
          }
      
          public UsersMenu UsersMenu { get; }
          public Home Home { get; }
          public DropdownMenu2 DropdownMenu2 { get; }
      
          private class UsersMenuImpl : UsersMenu
          {
          }
      
          private class HomeImpl : Home
          {
          }
      
          private class DropdownMenu2Impl : DropdownMenu2
          {
          }
      }
      

      开发人员可以查看和使用UsersMenuHomeDropdownMenu2 abstract 类,但不能创建它们的实例。只有HeaderNavigationMenu可以。

      当然,其他开发人员始终可以从public abstract 派生类创建自己的类,但您能做的只有这么多。 UsersMenuHomeDropdownMenu2 必须是 public 才能成为 public 属性。

      【讨论】:

      • 感谢您的意见。但我不确定这是否会按预期工作。由于“public UsersMenu UsersMenu { get; }”代码行,私有类(HomeImpl、UsersMenuImpl ...)中没有任何方法可以使用。如果您更改为“public UsersMenuImpl UsersMenu { get; }”,则会收到错误消息“不一致的可访问性:属性类型 UsersMenuImpl 的可访问性低于属性 UsersMenu”
      • 您不会将任何方法放在私有类中。所有方法都进入类的public abstract 版本。私有版本实际上只存在以便您可以构造一个实例。私有类将是空的,除了它们可能需要的任何构造函数。
      • 好的,所以其他程序员会知道不要将这些类用作独立对象。这很漂亮。感谢您的进一步解释。
      猜你喜欢
      • 2011-07-06
      • 1970-01-01
      • 2013-10-09
      • 2011-05-09
      • 2011-06-30
      • 2011-07-18
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多