Simon V.
Simon V.

Reputation: 1485

Design pattern for private members access?

Let's use this simple example :

Connect4Board.cs :

public class Connect4Board
{
    private Box[,] _boxes = new Box[7, 6];

    public void DropPieceAt(int column, bool redPiece)
    {
        //Safe modifications to box colors.
    }        

    public Box GetBoxAt(int x, int y)
    {
        return _boxes[x, y];
    }
}

Box.cs :

public class Box
{
    public bool IsRed { get; private set; }
    public bool IsEmpty { get; private set; }
}

I want GetBoxAt() to return a box with read-only properties. However I want my Connect4Board to be able to change boxes colors.

Let's assume that I don't want to use internal modifier at all.

My solution (quite ugly) :

public class Connect4Board
{
    private Box.MutableBox[,] _mutableBoxes = new Box.MutableBox[7, 6];

    public Connect4Board()
    {
        for (int y = 0; y < 6; y++)
        {
            for (int x = 0; x < 7; x++)
            {
                _mutableBoxes[x, y] = new Box.MutableBox();
            }
        }
    }

    public void DropPieceAt(int column, bool isRed)
    {
        //Safe modifications to box colors.
    }        

    public Box GetBoxAt(int x, int y)
    {
        return _mutableBoxes[x, y].Box;
    }
}

public class Box
{
    public bool IsRed { get; private set; }
    public bool IsEmpty { get; private set; }

    private Box()
    {
    }

    public class MutableBox
    {
        public Box Box { get; private set; }

        public MutableBox()
        {
            Box = new Box();
        }

        public void MakeRed() { //I can modify Box here }

        public void MakeYellow() { //I can modify Box here }

        public void MakeEmpty() { //I can modify Box here }
    }
}

Is there a good design pattern to make this more elegant ?

Upvotes: 4

Views: 619

Answers (4)

Tejas Sharma
Tejas Sharma

Reputation: 3440

Solution 1

You could create a wrapper around Box which is immutable. Connect4Board would use the MutableBox class internally but would expose ImmutableBox to consumers.

public interface IBox
{
    bool IsRed { get; }
    bool IsEmpty { get; }
}

public class MutableBox : IBox
{
    public bool IsRed { get; set; }
    public bool IsEmpty {get; set; }
    public IBox MakeImmutable()
    {
        return new ImmutableBox(this);
    }
}

public class ImmutableBox : IBox 
{
    private IBox innerBox;
    public ImmutableBox(IBox innerBox) { this.innerBox = innerBox; }
    public bool IsRed { get { return innerBox.IsRed; } }
    public bool IsEmpty { get { return innerBox.IsEmpty; } }
}

public class Connect4Board
{
    private MutableBox[,] boxes = new MutableBox[7, 6];

    public void DropPieceAt(int column, bool redPiece)
    {
        // perform modifications
    }

    public IBox GetBoxAt(int x, int y)
    {
        return boxes[x,y].MakeImmutable();
    }
}

Solution 2

You could maybe use explicit interface implementation to achieve this? Create an interface IMutableBox.

public interface IMutableBox
{
    void SetIsRed(bool isRed);

    void SetIsEmpty(bool isEmpty);
}

public class Box : IMutableBox
{
    private bool isRed;
    private bool isEmpty;

    public bool IsRed { get { return isRed; } }
    public bool IsEmpty { get { return isEmpty; } }

    void IMutableBox.SetIsRed(bool isRed)
    {
        this.isRed = isRed;
    }

    void IMutableBox.SetIsEmpty(bool isEmpty)
    {
        this.isEmpty = isEmpty;
    }
}

Now, in order to mutate Box, you would need to cast it to an IMutableBox.

var box = new Box();
var mutableBox = box as IMutableBox;
mutableBox.SetEmpty(true);

Upvotes: 1

Dustin Kingen
Dustin Kingen

Reputation: 21245

You can make a ReadOnlyBox that can be a facade for your Box much like a ReadOnlyCollection.

[Flags]
public enum BoxState
{
    Empty = 0,
    Red = 1 << 0,
    Black = 1 << 1
}

[Flags]
public enum BoardColor
{
    Red = 1 << 0,
    Black = 1 << 1
}

public interface IBox
{
    BoxState State { get; }
}

public class Box : IBox
{
    public BoxState State { get; set; }
}

public class ReadOnlyBox : IBox
{
    private readonly IBox _box;

    public ReadOnlyBox(IBox box)
    {
        _box = box;
    }

    public BoxState State { get { return _box.State; } }
}

public class Connect4Board
{
    private const int _boardWidth = 7;
    private const int _boardHeight = 6;
    private Box[,] _boxes = new Box[_boardWidth, _boardHeight];

    public void DropPieceAt(int column, BoardColor color)
    {
        for(int height = 0; height < _boardHeight; height++)
        {
            if(_boxes[column, height].State != BoxState.Empty) continue;

            _boxes[column, height].State = (BoxState)color;
            break;
        }
    }        

    public IBox GetBoxAt(int x, int y)
    {
        return new ReadOnlyBox(_boxes[x, y]);
    }
}

Upvotes: 1

StriplingWarrior
StriplingWarrior

Reputation: 156469

There are a number of strategies you could use.

It's often useful to program to interfaces. The IBox interface below would not allow people to edit the box (without casting it to a Box), but still leaves your code simple.

public class Connect4Board
{
    private Box[,] _boxes = new Box[7, 6];

    public void DropPieceAt(int column, bool redPiece)
    {
        //Safe modifications to box colors.
    }        

    public IBox GetBoxAt(int x, int y)
    {
        return _boxes[x, y];
    }
}

public interface IBox
{
    bool IsRed { get; }
    bool IsEmpty { get; }
}

public class Box : IBox
{
    public bool IsRed { get; set; }
    public bool IsEmpty { get; set; }
}

Another approach would be to make boxes always immutable (like strings), and instead of modifying the states of your boxes, you just modify which box is in which location in your array:

public class Connect4Board
{
    private Box[,] _boxes = new Box[7, 6];

    public Connect4Board()
    {
        for(int i = 0; i<7; i++)
        {
            for(int j = 0; j<6; j++)
            {
                // Notice how you're not changing a color, but assigning the location
                _boxes[i,j] = Box.Empty;
            }
        }
    }

    public void DropPieceAt(int column, bool redPiece)
    {
        // Modifications to the top empty location in the given column.
    }        

    public Box GetBoxAt(int x, int y)
    {
        return _boxes[x, y];
    }
}

public class Box
{
    public bool IsRed { get; private set; }
    public bool IsBlack { get; private set; }
    public bool IsEmpty { get; private set; }

    private Box() {}

    public static readonly Box Red = new Box{IsRed = true};
    public static readonly Box Black = new Box{IsBlack = true};
    public static readonly Box Empty = new Box{IsEmpty = true};
}

Upvotes: 4

Charles Bretana
Charles Bretana

Reputation: 146449

WOuld this work for you? Make Box Immutable, with static factory, and add static properties that return new boxes with various colors

  public class Box
  {
       private Box() {}
       private Box(Color color) { Color = color; }
       public static Box Make(Color color) { return new Box(color); }
       public static Box RedBox { get { return new Box(Color.Red); } }
       public static Box GreenBox { get { return new Box(Color.Green); } }
       public static Box BlueBox { get { return new Box(Color.Blue); } }
       //   ... etc.
   }

Upvotes: 3

Related Questions