4

My Scenario : Consider two classes class SubGroup, and class Group. I want no other classes to create SubGroup's object without a Group, because SubGroups will not exist without Group. But, Group can exist without any SubGroup . Also, Group can contains multiple SubGroup's.

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.
 }   

What I have tried : I could make Group to inherit from SubGroup and make SubGroup constructor as protected, but that says is-a releationship, that is every Group is a SubGroup. But actually it is not.

Moreover I want an arg to create a subGroup, but I have no argument for subgroup during the creation of Group, hence I cannot use inheritance.

Inner Classes : Inner classes will not help because I have SuperGroup,SubGroup,MicroGroup many layers. Actually it is an financial project there, I have Account,AccountHead,SubGroup,Group etc.. so too may inner layers will be created. which will reduce the readability.

How could I achieve in C#?

Muthu Ganapathy Nathan
  • 3,199
  • 16
  • 47
  • 77
  • Do you want the type SubGroup to be visible outside of Group? If not you can nest the SubGroup definition within Group. If you need some sort of visibility of SubGroup's members outside of Group you could define a public interface to SubGroup and only expose that. – James Gaunt May 08 '13 at 11:39
  • how about inner classes? http://stackoverflow.com/questions/804453/using-inner-classes-in-c-sharp this also depends if you want to be able to interact with a subgroup outside of the definition of the Group class. – Ric May 08 '13 at 11:39
  • You could make SubGroup an inner (nested) private class of Group. – Eyvind May 08 '13 at 11:40
  • 1
    no inner classes will not help because I have SuperGroup,SubGroup,MicroGroup many layers. Actually it is an financial project there, account,account head,sub-group,group etc.. soo too may inner layers will be created. – Muthu Ganapathy Nathan May 08 '13 at 11:40
  • 6
    Nested classes is the only way to control visibility at a class level. If you can't use nested classes you're stuck to controlling visibility at an assembly level (i.e. use internal). In other words C# does not have the concept of friend classes. – James Gaunt May 08 '13 at 11:42
  • 1
    If you define Group and Subgroup in the same assembly and make the Subgroup constructor `internal` then nothing outside that assembly will be able to create a Subgroup. Would that be enough? – Matthew Watson May 08 '13 at 11:49
  • Sincerely I don't understand why you want only Group to build SubGroup. If the SubGroup needs a Group then make it a requirement by passing it to the ctor and throwing if null. Else your code is saying "a SubGroup doesn't require a group" and then why imposing limits? Except for using an interface what you want is clunky in C# and for a reason: your requirements and what you're asking for are different. – fog May 08 '13 at 11:50
  • Ya. I have to learn `internal`. I am from java background – Muthu Ganapathy Nathan May 08 '13 at 11:50
  • No one has actually asked what the behaviour of Group and Subgroup are. Also where is the solution in which Group is actually responsible for Creating the SubGroup. The question is more correct than the answers. – Alistair May 08 '13 at 14:00

6 Answers6

7

I want no other classes to create SubGroup's object other than Group, because SubGroups will not exist without Group.

Why not just inverse a dependency in declaration:

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;
  }

 }

So all this you will use like:

var group = new Group(); 
var subgroup = Subgroup.Create(group, 2); 
Tigran
  • 61,654
  • 8
  • 86
  • 123
  • I don't see how this prevents classes other than Group from creating a Subgroup, considering that `Subgroup.Create()` is public and it creates a `Subgroup`. – Matthew Watson May 08 '13 at 12:12
  • 1
    @MatthewWatson @Tigran Actually Its my fault. But Tigran is correct. My sentence should be `I want no other classes to create SubGroup's object without an Group object, because SubGroups will not exist without Group.` – Muthu Ganapathy Nathan May 08 '13 at 12:19
  • 1
    @EAGER_STUDENT In that case, this must be the correct answer (I've +1ed it). :) – Matthew Watson May 08 '13 at 12:23
  • @Tigran Actually I also want to accept your answer too, but the one I accepted have said the same and I actually got the point form him before reading your answer. So,I go for him. – Muthu Ganapathy Nathan May 08 '13 at 12:34
  • @Matthew The comment http://stackoverflow.com/questions/16439653/object-oriented-paradigm-c-sharp?noredirect=1#comment-23583145 sounds logical for me. – Muthu Ganapathy Nathan May 08 '13 at 14:18
6

You should think about if you really need a type distinction between your different levels of groups. In many cases, it is enough to just build a composite pattern where an object can have multiple subobjects of the same (or a common) type. This allows you to nest the objects in a free matter.

Instead you should think about if your different group levels are actually different in behavior. What is the difference between a level 2 group and a level 3 group for example? Do they share the same behavior except for their parent and child group level? If so, you should definitely combine them.

Another thing you should consider if you even need the restriction that nobody except the parent group is allowed to create a subgroup. Why do you even need that? It makes sense to restrict that nobody except the group can add subgroups to itself, but object creation is usually not really an issue (instead it’s actually a lot more helpful if you can freely create the objects without further dependencies—this allows you to test it!).

For example your groups could look like this:

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);

You could even make a special subtype which corresponds to your “root” group, and all the other groups have a protected constructor and can only created by their own type (Group or the root group).

poke
  • 369,085
  • 72
  • 557
  • 602
  • +1 this is the most flexible approach to solving the problem. it's also the least painful when the organizational structure changes (which tends to happen a lot) – devnull May 08 '13 at 12:02
  • @Tigran The comment http://stackoverflow.com/questions/16439653/object-oriented-paradigm-c-sharp?noredirect=1#comment-23583145 sounds logical for me. – Muthu Ganapathy Nathan May 08 '13 at 14:17
5

Cannot be created without a specified object exists? Sounds like a constructor injection to me.

public class SubGroup{
    public SubGroup(Group group){
        if(group == null) {
            throw new InvalidOperationException("A subgroup must has a group");
        }
        this.group = group;
    }
    Group group;
}

This way, you will not able to create a SubGroup without group, and if you pass null, it will throw runtime exception. The backside? Cannot be serialized and cannot be used in ORM.

Not sure if this meets your requirement though.

Fendy
  • 4,565
  • 1
  • 20
  • 25
  • 1
    This isn't what OP asked, but probably how his application should work. One shouldn't care which classes can or cannot instantiate other classes, but what prerequisites must be met so that a valid object can be created. – Corak May 08 '13 at 11:51
  • Well, actually I just don't like a static entity create whatever method. If the SubGroup should has one and only one group, then the logic should reside at SubGroup class, or at least in the SubGroup Builder. – Fendy May 08 '13 at 12:56
4

You can use interface and declare SubGroup as private inner class:

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); 
    }
}

Edit:

If inner class is not your option, another option you can think of is to separate assembly, in your case you can make one assembly just only contains Group, SubGroup and ISubGroup, and declare SubGroup as internal class:

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); 
    }
}
cuongle
  • 74,024
  • 28
  • 151
  • 206
1

From the description that you give, I'd say you are possibly looking at this problem the wrong way - you're trying to limit the visibility of the constructor because of a restriction in your program's model, but there are other possible solutions.

Some thoughts that may provide alternatives:

  • you only want to create a Subgroup if there is a Group parent, but that doesn't mean the class doesn't/can't function in isolation. If the class functions in isolation, perhaps you should consider that your preferred usage isn't actually a restriction. (This is analogous to the argument against the singleton pattern: "there can be only one" versus "there is only one.") What would happen if a subgroup were created without a group?

  • is there an actual relationship from the Subgroup to the Group? If so, make it explicit in the constructor. If your constructor is public Subgroup(Group parent) then you can be assured that - no matter where it's created - there will always be a Group associated.

  • is there a distinction between the two classes? Could you reuse Group in both cases and have an optional parent/child property for the hierarchy, or is there actually a difference in functionality between the two?

  • can you use an assembly boundary to your advantage? You could mark the Subgroup constructor as internal and then ensure that the code within that assembly respects its intended use.

Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
1

My answer is that you should simply declare as internal all the constructors that you want to be hidden from external classes.

If you do that, only classes within the same assembly (i.e. "class library project") will be able to access them.

Other than that, you should follow the advice in some of the other answers that recommend a different architecture.

However it is possible to make the constructor private and call it via reflection.

Or rather than call the constructor directly, you could have a private static method in SubGroup which returns a factory method, and call that via reflection. This makes it somewhat less fiddly to call the constructor (and avoids boxing operations during construction).

I advise against doing this; You should find a cleaner way, such as using an internal constructor or a different architecture, but just so you can see how you could do it:

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;
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276