1
\$\begingroup\$

as a practice and selfdevelopment exercise I have decided to implement design patterns in C#. I am using polish cuisine as an example in my implementations. In this project I implemented Singleton design pattern. I kindly ask for a review :D Here is the code:

Model:

public sealed class Pierogi
{
    public Guid Guid { get; }
    public int Count { get; }
    public string Type { get; }
    
    public Pierogi(int count, string type)
    {
        Guid = Guid.NewGuid();
        Count = count;
        Type = type;
    }

    public override string ToString()
    {
        return $"Guid: {Guid}, Count: {Count}, Type: {Type}";
    }
}

public static class PierogiTypes
{
    public const string WithPotatoesAndCheese = "with potatoes and cheese";
    public const string WithGroat = "with groat";
    public const string WithCabbage = "with cabbage";
    public const string WithMeat = "with meat";
    public const string WithStrawberries = "with strawberries";
    public const string WithBlueberries = "with blueberries";
    public const string WithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup = "with potatoes and cheese but also with yoghurt and ketchup";
}

Singleton:

public sealed class PierogiSingleton : IPierogi
{
    private static readonly Lazy<PierogiSingleton> LazyInstance = new(() => new PierogiSingleton());

    public static PierogiSingleton Instance => LazyInstance.Value;
    public static bool IsInstanceCreated => LazyInstance.IsValueCreated;
    public int PlatesServed = 0;

    private PierogiSingleton()
    {
    }

    public Pierogi ServePierogiWithPotatoesAndCheese()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithPotatoesAndCheese);
    }

    public Pierogi ServePierogiWithGroat()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithGroat);
    }

    public Pierogi ServePierogiWithCabbage()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithCabbage);
    }

    public Pierogi ServePierogiWithMeat()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithMeat);
    }

    public Pierogi ServePierogiWithStrawberries()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithStrawberries);
    }

    public Pierogi ServePierogiWithBlueberries()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithBlueberries);
    }

    public Pierogi ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup()
    {
        PlatesServed += 1;
        return new Pierogi(10, PierogiTypes.WithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup);
    }
}

Program.cs:

class Program
{
    public static async Task Work(int delay)
    {
        await Task.Delay(delay);
        Console.WriteLine($"Is pierogi singleton instantiated: {PierogiSingleton.IsInstanceCreated}");
        await Task.Delay(delay);
        var firstPortionOfPierogi = PierogiSingleton.Instance.ServePierogiWithBlueberries();
        Console.WriteLine($"{firstPortionOfPierogi}, Yummy :D");
        Console.WriteLine($"Is pierogi singleton instantiated: {PierogiSingleton.IsInstanceCreated}");
        await Task.Delay(delay);
        var secondPortionOfPierogi = PierogiSingleton.Instance.ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup();
        Console.WriteLine($"{secondPortionOfPierogi}, aaaah what is this sacrilege? :O");
        Console.WriteLine($"Plates served {PierogiSingleton.Instance.PlatesServed}");
    }
    
    public static async Task Main()
    {
        Task.WaitAll(new Task[]
        {
            Task.Run(() => Work(500)),
            Task.Run(() => Work(2500))
        });
    }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Implementing the Singleton Pattern in C# \$\endgroup\$ Dec 25, 2022 at 15:21
  • 1
    \$\begingroup\$ @AlexanderPetrov thank you for this link. I have seen this article and read it from cover to cover. Nevertheless I have decided to share this small project to you. And I am glad I did since comments were very helpful :) \$\endgroup\$
    – Artur
    Dec 25, 2022 at 15:27

1 Answer 1

3
\$\begingroup\$

You want a Singleton, you got a Singleton with these three(ish) lines:

private static readonly Lazy<PierogiSingleton> LazyInstance = new(() => new PierogiSingleton());

public static PierogiSingleton Instance => LazyInstance.Value;

private PierogiSingleton()
{
}

QED. However, naming the class PierogiSingleton is not indicative of the purpose of the class, but rather of how it is implemented. Implementation details should be of no concern to its consumer. From what it seems to do, it manages assembling and serving up Pierogi dishes of varying sorts. But it also seems to keep track of the number of dishes served up. This may be indicative of splitting the responsibilities into separate classes. Let's name one PierogiFactory (to use a software term), create a second one called PierogiDishCounter and lastly one that brings those two together and call it PierogiRestaurant . Also, you could consider more strongly-typing your Pierogis by using an enum rather than a string for its type. And there are multiple ways a search away to add textual descriptors to enums via attributes. So let's bring that together:

    public sealed class Pierogi
    {
        public Guid Guid { get; }

        public int Count { get; }

        public PierogiTypes Type { get; }

        public Pierogi(int count, PierogiTypes type)
        {
            Guid = Guid.NewGuid();
            Count = count;
            Type = type;
        }

        public override string ToString()
        {
            return $"Guid: {Guid}, Count: {Count}, Type: {Type}";
        }
    }

    public enum PierogiTypes
    {
        [Description("with potatoes and cheese")]
        WithPotatoesAndCheese,

        [Description("with groat")]
        WithGroat,

        [Description("with cabbage")]
        WithCabbage,

        [Description("with meat")]
        WithMeat,

        [Description("with strawberries")]
        WithStrawberries,

        [Description("with blueberries")]
        WithBlueberries,

        [Description("with potatoes and cheese but also with yoghurt and ketchup")]
        WithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup,
    }
    public interface IPierogi // guessing
    {
        Pierogi ServePierogiWithPotatoesAndCheese();

        Pierogi ServePierogiWithGroat();

        Pierogi ServePierogiWithCabbage();

        Pierogi ServePierogiWithMeat();

        Pierogi ServePierogiWithStrawberries();

        Pierogi ServePierogiWithBlueberries();

        Pierogi ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup();
    }
    public sealed class PierogiRestaurant : IPierogi
    {
        private static readonly Lazy<PierogiRestaurant> LazyInstance = new(() => new PierogiRestaurant(PierogiDishCounter.Instance, PierogiFactory.Instance));

        private readonly IPierogiDishCounter pierogiDishCounter;

        private readonly IPierogi pierogiFactory;

        public static PierogiRestaurant Instance => LazyInstance.Value;

        public static bool IsInstanceCreated => LazyInstance.IsValueCreated;

        private PierogiRestaurant(IPierogiDishCounter pierogiDishCounter, IPierogi pierogiFactory)
        {
            this.pierogiDishCounter = pierogiDishCounter;
            this.pierogiFactory = pierogiFactory;
        }

        public int PlatesServed => pierogiDishCounter.PlatesServed;

        public Pierogi ServePierogiWithPotatoesAndCheese()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithPotatoesAndCheese();
        }

        public Pierogi ServePierogiWithGroat()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithGroat();
        }

        public Pierogi ServePierogiWithCabbage()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithCabbage();
        }

        public Pierogi ServePierogiWithMeat()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithMeat();
        }

        public Pierogi ServePierogiWithStrawberries()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithStrawberries();
        }

        public Pierogi ServePierogiWithBlueberries()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithBlueberries();
        }

        public Pierogi ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup()
        {
            pierogiDishCounter.ServePlate();
            return pierogiFactory.ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup();
        }
    }
    public sealed class PierogiFactory : IPierogi
    {
        private static readonly Lazy<PierogiFactory> LazyInstance = new(() => new PierogiFactory());

        public static PierogiFactory Instance => LazyInstance.Value;

        public static bool IsInstanceCreated => LazyInstance.IsValueCreated;

        private PierogiFactory()
        {
        }

        public Pierogi ServePierogiWithPotatoesAndCheese()
        {
            return new Pierogi(10, PierogiTypes.WithPotatoesAndCheese);
        }

        public Pierogi ServePierogiWithGroat()
        {
            return new Pierogi(10, PierogiTypes.WithGroat);
        }

        public Pierogi ServePierogiWithCabbage()
        {
            return new Pierogi(10, PierogiTypes.WithCabbage);
        }

        public Pierogi ServePierogiWithMeat()
        {
            return new Pierogi(10, PierogiTypes.WithMeat);
        }

        public Pierogi ServePierogiWithStrawberries()
        {
            return new Pierogi(10, PierogiTypes.WithStrawberries);
        }

        public Pierogi ServePierogiWithBlueberries()
        {
            return new Pierogi(10, PierogiTypes.WithBlueberries);
        }

        public Pierogi ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup()
        {
            return new Pierogi(10, PierogiTypes.WithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup);
        }
    }

    public interface IPierogiDishCounter
    {
        int PlatesServed { get; }

        void ServePlate();
    }
    public sealed class PierogiDishCounter : IPierogiDishCounter
    {
        private static readonly Lazy<PierogiDishCounter> LazyInstance = new(() => new PierogiDishCounter());

        public int PlatesServed { get; private set; } = 0;

        public static PierogiDishCounter Instance => LazyInstance.Value;

        private PierogiDishCounter()
        {
        }

        public void ServePlate()
        {
            PlatesServed += 1;
        }
    }
    class Program
    {
        public static async Task Work(int delay)
        {
            await Task.Delay(delay);
            Console.WriteLine($"Is pierogi singleton instantiated: {PierogiRestaurant.IsInstanceCreated}");
            await Task.Delay(delay);
            var firstPortionOfPierogi = PierogiRestaurant.Instance.ServePierogiWithBlueberries();
            Console.WriteLine($"{firstPortionOfPierogi}, Yummy :D");
            Console.WriteLine($"Is pierogi singleton instantiated: {PierogiRestaurant.IsInstanceCreated}");
            await Task.Delay(delay);
            var secondPortionOfPierogi =
                PierogiRestaurant.Instance.ServePierogiWithPotatoesAndCheeseButAlsoWithYoghurtAndKetchup();
            Console.WriteLine($"{secondPortionOfPierogi}, aaaah what is this sacrilege? :O");
            Console.WriteLine($"Plates served {PierogiRestaurant.Instance.PlatesServed}");
        }

        public static async Task Main()
        {
            Task.WaitAll(new Task[] { Task.Run(() => Work(500)), Task.Run(() => Work(2500)) });
        }
    }

```
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Although I agree with the decomposition in general. The dish counter is probably too simple to be modelled as a class. Integer itself has all the capabilities of the dish counter interface and so having an integer counter inside the restaurant class could be good enough. It might make more sense if we have new requirement like tracking all the resources given out with our dishes. We then want to pass pierogi instances to this tracker and let it track number of plates, amount of sold potatoes, cheese, etc. At this point I would feel far less overengineeting than if I was just tracking +1's... \$\endgroup\$
    – slepic
    Dec 25, 2022 at 9:31
  • \$\begingroup\$ Thank you Jesse and Slepic for your comments. Thank you for great feedback and time you dedicated in this thread. I upvoted your comments and marked them as answer. Indeed I have made a rookie mistake when naming the class - giving the notion of how it is implemented and not what it does is something that should be avoided. I agree with Slepic that dish counter is probably too simple to make another class from it. I will make adjustments into my code. Thank you again for your suggestions :D \$\endgroup\$
    – Artur
    Dec 25, 2022 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.