1

Background: Picture an artwork of a shape where within it is multiple levels of nested shapes. Change of properties e.g. area and length of any of those shape-within-a-shape will cause all the related properties and shapes to change.

I have a design pattern which goes like this:

I have an object graph called (for discussion sake) "NestedShapes" that has tons of properties which are related to each other, for example, "Area" and "Length". But the graph is designed to be dumb, i.e. given either value, it doesn't know how to calculate the other and will not do so.

What happens instead is that the graph can be attached to a GraphManager which takes the top level root node IRootShape in its contructor.

NestedShapes implements IRootShape which also implements INotifyPropertyChanged. GraphManager subscribes to those property changes, and runs the logic to calculate related fields and set the graph to the correct state via IRootShape.

Problem: Along with IRootShape, I have IShape, ISquare, ICircle etc. which are real C# interfaces. But the problem is for some of these properties I only want them to have setters that are private to GraphManager. I know the implementing shape can still expose a public setter, but I do not want to necessarily expose these on the UI side to be able to set the property from GraphManager. What should I do? Is base classes with internal set the way to go?

Jake
  • 11,273
  • 21
  • 90
  • 147
  • 1
    Interface by definition is declaring what should be externally visible. I would venture to say a base class would be your best bet. – Brad Christie Jan 12 '12 at 01:32
  • If you want common behavior for all the shapes, then they need to inherit from a common base class. Also, Why do you need separate interfaces for `ICircle`, `ISquare` etc? Why not just `IShape` only. – John Alexiou Jan 12 '12 at 02:12
  • @ja72 I guess i would say that IShape, ISquare, ICircle only have states. The behavior is in the GraphManager. – Jake Jan 12 '12 at 02:47
  • Can you just build the logic into your shape classes, encapsulate the pivate data, and eliminate the `GraphManager` class? Why design a class to be dumb? – Jeff Sternal Jan 12 '12 at 02:50

2 Answers2

2

Let GraphManager interact with the Base classes.

Everything else interacts with the interfaces only.

Do not expose properties in your interface.

public class Circle : ICircle{
   public double Radius{
      get;set;
   }

   /* blah blah ... */
}

public interface ICircle {
   /* No properties */

   /* blah blah ...*/
}
Daryl Teo
  • 5,394
  • 1
  • 31
  • 37
  • +1. Also if needed you can move getter to interface, but implementation will be free to have internal/private setter as needed. – Alexei Levenkov Jan 12 '12 at 01:55
  • @Daryl I get the base class idea. But I don't understand the purpose of empty interface in this case. Can explain please? – Jake Jan 12 '12 at 02:49
  • @Jake, It is your code - want to have interface with properties - add them, don't need an interface at all - don't create one. – Alexei Levenkov Jan 12 '12 at 02:59
  • @Jake: the interface is not empty. I just put blah blah blah because I don't know what it was supposed to do. – Daryl Teo Jan 12 '12 at 03:00
  • @AlexeiLevenkov its just that Darly said `Everything else interacts with the interfaces only` and I thought it is a recommendation. – Jake Jan 12 '12 at 03:03
  • @DarylTeo ah.. ok. I get it. Thanks. – Jake Jan 12 '12 at 03:03
1

The trick to do this via interfaces is to use two separate interfaces; one public and a second which is internal-only. Yes, it requires a little more code as you have to explicitly implement the internal interface, manually delegating its property contracts down to the actual properties, but I just hide that in a partial class in a file called 'ClassName.Internal.cs'

This way you can clearly and cleanly expose the interfaces with the exact permissions that you want, all without having to resort to base classes, so this technique can be used to extend existing object graphs as well.

Here's an example of a TestItem with public getters and internal setters, all accessible via interfaces.

Here's the main class file with the public interface, stored in TestItem.cs

public interface ITestItem
{
    ModelItem  Owner { get; }
    ModelScope Scope { get; }
}

public partial class TestItem : ITestItem
{
    // These implicitly handle ITestItem since the getters are public
    public ModelItem  Owner { get; internal set; }
    public ModelScope Scope { get; internal set; }
}

Here's the internal-only implementation stored in TestItem.Internal.cs implemented via a partial class.

internal interface IWritableTestItem
{
    ModelItem  Owner { get; set; }
    ModelScope Scope { get; set; }
}

public partial class TestItem : IWritableTestItem
{
    ModelItem IWritableTestItem.Owner
    {
        get => Owner;
        set => Owner = value;
    }

    ModelScope IWritableTestItem.Scope
    {
        get => Scope;
        set => Scope = value;
    }
}

Hope this helps!

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286