面向对象编程范式 - C#

4

我的场景: 考虑两个类class SubGroup和class Group。 我希望没有其他类创建SubGroup对象,除非有一个Group,因为没有Group就不会有SubGroups存在。 但是, Group可以存在而没有任何SubGroup。 此外,Group可以包含多个SubGroup。

class SubGroup{
  private SubGroup(int arg){
  }

 }

class Group{  
   List<SubGroup> list;
   Group(int param){
     list  = new List<SubGroup>();
   }

   SubGroup createChild(int args){
       return new SubGroup(int args);  // I need to access SubGroups constructor?? (without using inheritance).
   }
 }


 class C{
      Group b = new Group(param);
      SubGroup a = b.createChild(args); // since constructor is not visible here. bcz, SubGroup  will not exist without Group.
 }   
我尝试过的方法: 我可以让Group从SubGroup继承,并将SubGroup构造函数设置为保护,但这意味着is-a关系,即每个Group都是一个SubGroup。但实际上并不是这样。
此外,我想在创建子组时使用一个参数,但是在创建Group时没有子组的参数,因此我无法使用继承。 内部类: 内部类不能帮助解决问题,因为我有很多层SuperGroup,SubGroup,MicroGroup。 实际上这是一个金融项目,我有Account、AccountHead、SubGroup、Group等,所以会创建太多的内部层级,这会降低可读性。
如何在C#中实现?

你想让SubGroup类型在Group外可见吗? 如果不是,你可以将SubGroup定义嵌套在Group内部。 如果你需要在Group之外访问SubGroup的成员,你可以定义一个公共接口来访问SubGroup,并只暴露那个接口。 - James Gaunt
1
不使用内部类是不行的,因为我有SuperGroup、SubGroup、MicroGroup等多层嵌套。实际上这是一个金融项目,有账户、账户头、子组、组等等,所以会创建太多的内部层级。 - Muthu Ganapathy Nathan
6
嵌套类是控制类级别可见性的唯一方法。如果不能使用嵌套类,则只能在程序集级别进行可见性控制(例如使用 internal)。换句话说,C#中没有友元类的概念。 - James Gaunt
1
如果您在同一个程序集中定义了Group和Subgroup,并使Subgroup的构造函数为“internal”,那么该程序集之外的任何内容都将无法创建Subgroup。那是否足够? - Matthew Watson
我真的不明白为什么你只想让Group来构建SubGroup。如果SubGroup需要一个Group,那就通过将其传递给ctor并在null时抛出异常来使其成为必需品。否则,你的代码就是在说“SubGroup不需要一个group”,那么为什么要强加限制呢?除了使用接口外,你想要的在C#中很笨拙,原因是:你的要求和你所要求的是不同的。 - fog
显示剩余4条评论
6个回答

7
我希望只有Group类可以创建SubGroup对象,因为没有Group,就不会有SubGroup存在。
为什么不在声明中反转依赖关系呢:
class Group{  
   List<SubGroup> list;
   ... 
   public void AddSubgroup(SubGroup sub) {
       list.Add(sub);
   }
 }

class SubGroup{

  ///private ctor, can be used only inside class
  private SubGroup(int arg){
  }

  public static Subgroup Create(Group parent, int arg) {
       Subgroup sub = new Subgroup(arg);//use private ctor
       parent.AddSubgroup(sub);
       return sub;
  }

 }

所以,你将使用所有这些,如下所示:
var group = new Group(); 
var subgroup = Subgroup.Create(group, 2); 

我不明白这如何防止除了Group以外的类创建Subgroup,因为Subgroup.Create()是公共的并且它会创建一个Subgroup - Matthew Watson
1
@MatthewWatson @Tigran 实际上这是我的错。但是Tigran是正确的。我的句子应该是“我不希望其他类在没有Group对象的情况下创建SubGroup对象,因为没有Group,就不会有SubGroups存在。” - Muthu Ganapathy Nathan
1
@积极学生 在这种情况下,这一定是正确的答案(我点了+1)。 :) - Matthew Watson
@Tigran,实际上我也想接受你的答案,但是我已经在阅读你的答案之前从另一个回答者那里得到了相同的观点。因此,我选择了他的答案。 - Muthu Ganapathy Nathan
@Matthew 这个评论对我来说听起来很合理。 - Muthu Ganapathy Nathan

6
你应该考虑一下,是否真的需要在不同级别的组之间进行类型区分。在许多情况下,只需构建复合模式,其中一个对象可以具有多个相同(或共同)类型的子对象。这样可以自由地嵌套对象。
相反,你应该考虑一下,你的不同组级别实际上是否具有不同的行为。例如,2级组和3级组之间有什么区别?除了它们的父级和子级组级别之外,它们是否共享相同的行为?如果是这样,你肯定应该将它们合并。
另一件你应该考虑的事情是,是否需要限制除父组以外的任何人都不能创建子组。你为什么需要这样做?限制除组以外的任何人向其添加子组是有意义的,但通常对象创建并不是真正的问题(相反,如果你可以自由地创建对象而没有进一步的依赖关系,那将非常有帮助——这使你可以测试它!)。
例如,你的组可能如下所示:
class Group
{
    private List<Group> childs;
    public Group(int arg)
    { ... }

    // pass an existing object
    public void AddSubgroup(Group g)
    {
        childs.add(g);
    }

    // or create the object inside
    public void AddSubgroup(int arg)
    {
        childs.add(new Group(arg));
    }
}

// used like this
Group b = new Group(param);
Group a = new Group(args);
b.AddSubgroup(a);

你甚至可以创建一个特殊的子类型,对应于你的“根”组,而所有其他组都有一个受保护的构造函数,只能由它们自己的类型(Group或根组)创建。

+1 这是解决问题最灵活的方法。当组织结构发生变化时(这种情况经常发生),它也是最不痛苦的。 - devnull
@Tigran,这个评论对我来说听起来很合理。https://dev59.com/2HHYa4cB1Zd3GeqPQ90M?noredirect=1#comment-23583145 - Muthu Ganapathy Nathan

5
没有指定对象存在就无法创建?这听起来像是构造函数注入。
public class SubGroup{
    public SubGroup(Group group){
        if(group == null) {
            throw new InvalidOperationException("A subgroup must has a group");
        }
        this.group = group;
    }
    Group group;
}

这样做,您将无法创建子组,必须先创建主组,否则传递null将会抛出运行时异常。缺点是?无法进行序列化并且无法应用于ORM。
不过我不确定这是否符合您的要求。

1
这不是 OP 所问的,但可能是他的应用程序应该工作的方式。人们不应该关心哪些类可以或不能实例化其他类,而是必须满足什么先决条件才能创建一个有效的对象。 - Corak
其实,我只是不喜欢静态实体创建任何方法。如果SubGroup应该有一个且仅有一个组,则逻辑应该驻留在SubGroup类中,或者至少在SubGroup Builder中。 - Fendy

4
您可以使用“interface”并将“SubGroup”声明为私有内部类:
您可以使用“interface”关键字声明一个接口,并将“SubGroup”作为私有内部类来定义:
public interface ISubGroup
{}

class Group
{
    private class SubGroup : ISubGroup
    {
        public SubGroup(int param)
        {
        }
    }

    List<SubGroup> list;

    public Group(int param)
    {
        list = new List<SubGroup>();
    }

    public ISubGroup createChild(int args)
    {
        return new SubGroup(args); 
    }
}

编辑:

如果内部类不是您的选择,您可以考虑另一种选择,即将装配分离。在您的情况下,您可以制作一个仅包含 GroupSubGroupISubGroup 的程序集,并将 SubGroup 声明为 internal 类:

public interface ISubGroup
{}

internal class SubGroup : ISubGroup
    {
        public SubGroup(int param)
        {
        }
    }

public class Group
{
    List<SubGroup> list;

    public Group(int param)
    {
        list = new List<SubGroup>();
    }

    public ISubGroup createChild(int args)
    {
        return new SubGroup(args); 
    }
}

1
OP 不想嵌套类。 - Ric

1
我的回答是,你应该将所有想要隐藏在外部类中的构造函数声明为internal
如果这样做,只有在同一程序集(即“类库项目”)中的类才能访问它们。
除此之外,你应该遵循其他答案中建议的不同架构的建议。
但是,可以使构造函数私有并通过反射调用它。
或者,而不是直接调用构造函数,你可以在SubGroup中拥有一个私有静态方法,返回一个工厂方法,并通过反射调用它。这使得调用构造函数变得更加简单(并且避免了构造期间的装箱操作)。 我建议不要这样做;你应该找到更清晰的方法,比如使用internal构造函数或不同的架构,但是只是为了让你看到如何做到这一点:
using System;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            Group group = new Group();
            SubGroup subGroup = group.MakeSubgroup(42);
            Console.WriteLine(subGroup.Value);
        }
    }

    public sealed class Group
    {
        public SubGroup MakeSubgroup(int value)
        {
            return _subgroupFactory(value);
        }

        static Func<int, SubGroup> getSubgroupFactory()
        {
            var method = (typeof(SubGroup)).GetMethod("getSubgroupFactory", BindingFlags.NonPublic | BindingFlags.Static);
            return (Func<int, SubGroup>) method.Invoke(null, null);
        }

        static readonly Func<int, SubGroup> _subgroupFactory = getSubgroupFactory();
    }

    public sealed class SubGroup
    {
        private SubGroup(int value)
        {
            this.value = value;
        }

        public int Value
        {
            get
            {
                return value;
            }
        }

        static Func<int, SubGroup> getSubgroupFactory()
        {
            return param => new SubGroup(param);
        }

        readonly int value;
    }
}

1
根据您提供的描述,我认为您可能是从错误的角度来看待这个问题 - 您试图限制构造函数的可见性,因为程序模型有限制,但还有其他可能的解决方案。
以下是一些可能提供替代方案的想法:
  • 只有在有一个Group父级时才需要创建Subgroup,但这并不意味着该类不能在独立环境下运行。如果该类可以在独立环境中运行,也许您应该考虑一下您的首选用法实际上并没有限制。(这类似于反对单例模式的论点:“只能有一个”与“确实只有一个”的区别。)如果没有组就创建了一个子组,会发生什么?

  • SubgroupGroup之间是否存在实际关系?如果是,要在构造函数中明确说明。如果您的构造函数是public Subgroup(Group parent),那么无论在哪里创建,都将始终有一个关联的Group

  • 这两个类之间是否有区别?您是否可以在两种情况下都重用Group,并为层次结构添加可选的父/子属性,或者这两种情况之间实际上存在功能差异?

  • 您能否利用程序集边界?您可以将Subgroup构造函数标记为internal,然后确保该程序集内部的代码尊重其预期用途。


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