Click here to Skip to main content
15,441,280 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have a class that I would like to present in two different ways: as an item that can be manipulated and configured by the user, and as an item that can only be displayed. In the example below I am using a shape that can be either drawn and changed, or just drawn.

Normally, to do this I would set up a base class (Shape) and two derived classes (ConfigurableParallelogram and DisplayOnlyParallelogram). I would usually only use interfaces as "contracts", so I would probably have an IShape interface to allow for different the abstractions of different shapes.

However, it occurred to me that interfaces are also "interfaces", like SQL Views, that should allow the same object to be viewed multiple ways. So I decided to try and set up an object hierarchy along those lines.

To begin with, I created my interfaces:

public interface IConfigurableShape
{
	public int Size { get; set; }
	public Brush Colour { get; set; }

	public void Draw();
	public IDisplayOnlyShape GetFinalVersion();
}

public interface IDisplayOnlyShape
{
	public int Size { get; }
	public Brush Colour { get; }
	public string Name { get; }

	public void Draw();
}


I then created a class that implements both interfaces.
public class Parallellogram : IConfigurableShape, IDisplayOnlyShape
{
	private int _size;
	private Brush _colour;
	private string _name;

	public Parallellogram()
	{
		_size = 1; // default
		_colour = Brushes.Black; // default
	}

	public Parallellogram(Parallellogram predefinedShape)
	{
		CopyConfigFrom(predefinedShape);
	}

	#region IConfigurableShape member implementations
	int IConfigurableShape.Size { get { return _size; } set { _size = value; } }
	Brush IConfigurableShape.Colour 
    { 
        get { return _colour; } 
        set { _colour = value; } 
    }

	IDisplayOnlyShape IConfigurableShape.GetFinalVersion()
	{
		_name = "use some name-determining logic here";
		return new Parallellogram(this);
	}

	void IConfigurableShape.Draw()
	{
		// draw the shape with dimensions and colour information
        // displayed to the user
	}
	#endregion

	#region IDisplayOnlyShape member implementations
	int IDisplayOnlyShape.Size { get { return _size; } }
	Brush IDisplayOnlyShape.Colour { get { return _colour; } }
	string IDisplayOnlyShape.Name { get { return _name; } }

	void IDisplayOnlyShape.Draw()
	{
		// draw the basic shape with the name displayed in the middle
	}
	#endregion

	public void CopyConfigFrom(IDisplayOnlyShape providedShape)
	{
		_size = providedShape.Size;
		_colour = providedShape.Colour;
		_name = providedShape.Name;
	}
}


My issue with this is that I have two constructors, each intended for the creation of different "views" of the object. But since constructors cannot be defined in the interfaces, I don't see a way to restrict the use of the two constructors. I would not want a display-only Parallelogram created with the first constructor since it would not ensure that the configuration of the object has been properly done (in this case, the Name property would not be set). But I don't see a way to prevent it.

Understandably, the code above could be easily changed to ensure that Size, Colour, and Name are always properly configured. It could also be easily refactored to use just one constructor. But this code is just a simplification for discussion purposes.

Is there a "standard" way to fix the multiple exposed constructors issue? Or is this whole example a gross anti-pattern that can only be handled by deleting the code, wiping my hard drive, and targeting my house with a tactical nuke?

What I have tried:

Looking up many online articles. However, all I have been able to find is discussions of inheriting multiple interfaces as a way of handling inheritance from multiple classes.
Posted
Updated 12-Feb-22 5:54am
v2
Comments
PIEBALDconsult 11-Feb-22 13:47pm     CRLF
Yes, but... Look at List<t> and its AsReadOnly Method. If you make one class with two interfaces (lower-case "i") the caller can simply cast it back to the other and modify it. So I recommend making two classes.
C Pottinger 11-Feb-22 15:24pm     CRLF
I see your point. Thank you for the speedy reply. In the future I will stick with creating two derived classes, but for this project, I will continue with the above structure and run the risk of being taken out by a Predator drone.
PIEBALDconsult 11-Feb-22 15:35pm    
Yes, don't worry about unlikely situations. Cover the more likely ones. YAGNI.

I think the interface definitions are part of the problem: IConfigurable is missing a "Name" in this context. And IConfigurable maybe should inherit IReadOnly instead.

.net - Should one interface inherit another interface - Stack Overflow[^]

On the other hand, you could use private constructor(s), and instead use static factory methods; e.g.

public static IReadOnly GetReadOnly(xxx) { return new something as ...}
 
Share this answer
 
v2
Comments
C Pottinger 17-Feb-22 15:24pm     CRLF
'IConfigurable is missing a "Name"' Actually, no. That was the idea. IConfigurableShape would not expose Name, but IDisplayOnlyShape would. 'IConfigurable maybe should inherit IReadOnly instead' I think you might have meant to say 'IDisplayOnlyShape would...'. That is the "read-only" interface. Perhaps my example was not clear. Despite that, you have offered what I think will be best solution: by having 2 private constructions, each called by different static factory methods. I can then have IConfigurableShape expose one method and IDisplayOnlyShape expose the other, thus limiting the use of the constructors. THANK YOU. However, PIEBALDconsult's comment above still holds true - I can't stop a user from casting from one type to another. Still, a little control is better than none.
I would simply add a bool parameter to the constructor (and an associated property) that defaults to the most-used value, and then change the desired properties to do nothing in the set method if the object is specied as read only.
 
Share this answer
 
Comments
C Pottinger 17-Feb-22 15:26pm    
I don't see how that would stop a user of the class from using the wrong constructor.
#realJSOP 18-Feb-22 6:24am     CRLF
The developer is responsible for determining the most appropriate constructor. There are a number of ways to handle this problem, including inheriting the class and override the properties. Pick the method that makes the most sense and move on with the project.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900