Home > Software design >  How to allow modification of Class properties only with Action<Class> method?
How to allow modification of Class properties only with Action<Class> method?

Time:07-06

I have class which handles some UI stuff, lets call that class and instance UI for the example.

inside this class I will have an object of type Colors:

public class Colors {
    public ConsoleColor Primary { get; set; } = ConsoleColor.White;
    public ConsoleColor Default { get; set;  } = ConsoleColor.Gray;
    public ConsoleColor Input { get; set; } = ConsoleColor.Gray;
    public ConsoleColor Success { get; set; } = ConsoleColor.Green;
    public ConsoleColor Error { get; set; } = ConsoleColor.Red;
    public ConsoleColor Highlight { get; set; } = ConsoleColor.Blue;
}

Inside UI I have this:

public static class UI {
    public static readonly Colors Colors = new();

    public static void ConfigureColors(Action<Colors> modification) {
        modification.Invoke(Colors);
    }
}

The problem is that I want the to be able to access the getters of the Colors property outside UI like this:

var primaryColor = UI.Colors.Primary;

but not the setters... right now, both of these work:

UI.ConfigureColors(colors => {
    colors.Primary = ConsoleColor.Yellow;
});

UI.Colors.Primary = ConsoleColor.Green;

but I only want to be able to set the colors using the ConfigureColors() method. I thought it would naturally be like this since I set the Colors property to readonly, but it doesn't work like I wanted.

I tried playing around with the accessability of the properties inside the Colors class, like making them readonly or "private set" but all the modification I thought of caused both the options to not work.

CodePudding user response:

You expose the colors through an interface declaring getter-only properties. An implementation can then have setters as well:

public interface IColors
{
    ConsoleColor Primary { get; }
    ConsoleColor Default { get; }
    ConsoleColor Input { get; }
    ConsoleColor Success { get; }
    ConsoleColor Error { get; }
    ConsoleColor Highlight { get; }
}

public class Colors : IColors
{
    public ConsoleColor Primary { get; set; } = ConsoleColor.White;
    public ConsoleColor Default { get; set; } = ConsoleColor.Gray;
    public ConsoleColor Input { get; set; } = ConsoleColor.Gray;
    public ConsoleColor Success { get; set; } = ConsoleColor.Green;
    public ConsoleColor Error { get; set; } = ConsoleColor.Red;
    public ConsoleColor Highlight { get; set; } = ConsoleColor.Blue;
}

Configure like this:

public static class UI
{
    public static readonly IColors Colors = new Colors();

    public static void ConfigureColors(Action<Colors> modification)
    {
        modification.Invoke((Colors)Colors);
    }
}

Example of usage:

var primary = UI.Colors.Primary;       // Okay

UI.Colors.Primary = ConsoleColor.Gray; // Error CS0200  Property or indexer 
                                       // 'ConfigurableColors.IColors.Primary'
                                       // cannot be assigned to --it is read only

CodePudding user response:

You could create an interface that only publishes the getters:

public interface  IColors {
    ConsoleColor Primary { get; }
    ConsoleColor Default { get; }
    ConsoleColor Input { get; }
    ConsoleColor Success { get; }
    ConsoleColor Error { get; }
    ConsoleColor Highlight { get; }
}

The class Colors should implement this interface:

public class Colors: IColors
{
  // ...
}

Instead of using the class for the property in the UI class, you use the interface:

public static class UI {
    private static readonly Colors _colors = new();

    public static IColors Colors { get => _colors; }

    public static void ConfigureColors(Action<Colors> modification) {
        modification.Invoke(Colors);
    }
}

This blocks write access to the properties when using the UI.Colors.Primary notation and allows writing with the actions - at least as long as the caller does not cast the interface to the class:

((Colors)UI.Colors).Primary = ConsoleColor.Green;

CodePudding user response:

You may split the Colors class into a readonly interface and a modification interface like this:

public interface IColors {
    ConsoleColor Primary { get; }
    ConsoleColor Default { get; }
    ConsoleColor Input { get; }
    ConsoleColor Success { get; }
    ConsoleColor Error { get; }
    ConsoleColor Highlight { get; }
}

public interface IModifyableColors {
    ConsoleColor Primary { get; set; }
    ConsoleColor Default { get; set; }
    ConsoleColor Input { get; set; }
    ConsoleColor Success { get; set; }
    ConsoleColor Error { get; set; }
    ConsoleColor Highlight { get; set; }
}

Both interfaces would be implemented by the class Colors (which could be made a private class):

class Colors : IColors, IModifyableColors
{
    public ConsoleColor Primary { get; set; } = ConsoleColor.White;
    public ConsoleColor Default { get; set;  } = ConsoleColor.Gray;
    public ConsoleColor Input { get; set; } = ConsoleColor.Gray;
    public ConsoleColor Success { get; set; } = ConsoleColor.Green;
    public ConsoleColor Error { get; set; } = ConsoleColor.Red;
    public ConsoleColor Highlight { get; set; } = ConsoleColor.Blue;
}

Your UI class would then expose the readonly interface only:

public static class UI {
    static readonly Colors _colors = new();

    public static IColors Colors => _colors;

    public static void ConfigureColors(Action<IModifyableColors> modification) {
        modification.Invoke(_colors);
    }
}
  • Related