Search code examples
c#encapsulation

encapsulation of subclasses from a constructor in C#


I am sorry if the question isn't too clear, but I did'nt know how else to phrase it.

I am attempting to create a card game which contains the following classes The suits are Red, Blue, Green, and Yellow and depending on the suit the value is the product of the cards number and the suit multiplier

red = 1

blue = 2

green = 3

yellow = 4

abstract class Card;

public class Deck
{
    private List<Card> deckList;
}

public class RedCard : Card, suit 
{
    private int number;

    public int Getvalue()
    {
        return number;
    }
}

interface suit
{
    int GetValue();
}

is there a way to encapsulate the Card subclasses so that the Deck constructor does not need to know what kind of cards can be added to the deck. The aim is to make sure that the Deck class does not need to be altered if I add another suit/card subclass in the future


Solution

  • If you think about this in real world terms, you have a Deck that has instances of Card in it. The types of attributes or physical properties of the cards in the deck are all the same, they all have a Suit and a Number and in your business case they all have a Value as well. From a structural point of view they are identical, all that changes are the values for each of the properties.

    If every Card has the same attributes, and the same behaviours, then there is no reason to create further sub-classes or even interfaces of these Cards.

    In software design, we use inheritance and composition (Interfaces) to add attributes and behaviours to the base implementation; or to change existing behaviours. It is an anti-pattern to inherit from a Card just to change the values of the attributes and doing so can elad to confusion down the track. You really need to separate the concept of structure vs content. If the structure and beahviour

    On top of this you have defined a list of suits and have declared that they have specific integer values, in C# we can encapsulate such fixed lists using an enum.

    public enum Suit : int
    {
        Red = 1,
        Blue = 2,
        Green = 3,
        Yellow = 4
    }
    
    public class Deck
    {
        private List<Card> deckList;
    }
    
    public class Card
    {
        public Suit Suit { get; private set; }
        public int Number { get; private set; }
    
        public Card (Suit suit, int number)
        {
            this.Suit = suit;
            this.Number = number;
        }
      
        public int Value { get { return (int)Suit * Number; } }
    }
    

    We can now create a method to generate a deck of cards for us, based on some fixed criteria, I'll call this from the constructor for this demo:

    public class Deck
    {
        private const int LENGTH_OF_SUIT = 10;
        private List<Card> deckList = new List<Card>();
        
        public Deck()
        {
            BuildDeck();
        }
    
        private void BuildDeck()
        {
            foreach (Suit suit in Enum.GetValues(typeof(Suit)))
            {
                for(int number = 1 ; number <= LENGTH_OF_SUIT; number ++)
                {
                    deckList.Add(new Card(suit, number));
                }
            }
        }
    }
    

    This simple structure is one way to encapsulates the requirements listed in the original post, you can play with this here: https://dotnetfiddle.net/BnhGGG


    If the number of suits can change at runtime, then an enum is NOT a good fit, then you would need a class to represent the Suit:

    public class Suit
    {
        public string Name { get;set; }
        public int Value { get;set; }
    }
    

    But note that the Card class doesn't need to change much:

    public class Card
    {
        public Suit Suit { get; private set; } 
        public int Number { get; private set; } 
    
        public Card (Suit suit, int number)
        {
            this.Suit = suit;
            this.Number = number;
        }
    
        public int Value { get { return Suit.Value * Number; } }
    }
    

    To build the deck we would need additional information, like what suits to build:

    public class Deck
    {
        private const int LENGTH_OF_SUIT = 10;
        private List<Card> deckList = new List<Card>();
        
        public Deck(Suit[] suits)
        {
            BuildDeck(suits);
        }
    
        private void BuildDeck(Suit[] suits)
        {
            foreach (Suit suit in suits)
            {
                for(int number = 1 ; number <= LENGTH_OF_SUIT; number ++)
                {
                    deckList.Add(new Card(suit, number));
                }
            }
        }
    }
    

    Finally, if we need to get all the Cards of a specific Suit we could add a method that does this for us to the Deck

    public List<Card> GetCardsOfSuit(Suit suit)
    {
        return deckList.Where(x => x.Suit == suit).ToList(); 
    }
    

    There are many other ways to implement the same or similar logic, this is but one example.


    You may have started off in your learning using Vehicle and then created sub classes for Car and MotorCycle. Often the example is that Vehicle has a property for Wheels and the Car has 4 Wheels and the MotorCycle has 2.

    That model alone is flawed as a learning tool, it can lead you to assume that the reason behind subclassing was to change the value of a fixed property. This example has been overly simplified, fundamentally there are other attributes and behaviours that give us real world, anc conceptual reasons to classify these objects into their own class definition.

    It was an example devised because in the real world classification of Cars and MotorCycles separately is very logical and it is easy to comprehend that they are both types of Vehicles.

    If we start to talk about Color of a Vehicle, then we are closer to the concept of a Suit of a Card. you can have a Red Car and a Red MotorCycle, but the color is only one of the attributes of that physical object, we do not now create a new class definition to represent RedCar and RedMotorCycle... Red is simply the Value of the Color property.

    A better inheritance example

    The example of an Animal that has sub classes of Bird and Fish makes it easier to show the similarities (what is inherited) and the differences in the types of attributes and beahviours encapsulated by the class definitions:

    We can have a count of eyes for all animals, and some type of animals by definition will only have a fixed number of eyes. So we can use eyes to show overriding fixed values. But it doesn't make sense to have a property on Animal to store the number of wings, because that is part of the definition that makes an animal a bird and no other type of animal will have wings at all. We wouldn't store the fact that a bird has any number of wings because All birds have 2 wings by definition. We would not normally even bother to record that fact in our model, because it is a constant value and not likely to be of any use to us. But Flight is a behaviour common to birds, but not all birds can fly! Now we can start to talk about behaviours.

    In this model we will capture 2 types of real world behaviours as properties, we wont specifically add any c# behaviours in these definitions, but it is a better tool to enable new developers to relate these abstract c# concepts to the real world.

    public class Animal
    {
        public string Name { get; set; }
    
        /// <summary>Number of Eyes</summary>
        /// <remarks>Not all animals have eyes, use 0 to represent no eyes</remarks>
        public virtual int Eyes { get; set; };
    
        public string override ToString()
        {
            return $"{Name} - Eyes:{Eyes}";
        }
    }
    
    /// <summary>Vertebrates have a spine, but are also `Chordates`, they have "Camera Eyes" that are specifically 2 eyes using lenses to focus an image.</summary>
    /// <remarks>http://www.madsci.org/posts/archives/1999-02/920061344.Ev.r.html#:~:text=This%20is%20the%20same%20process%20at%20work%20in,they%20retained%20the%20trait%20from%20a%20common%20ancestor.</remarks>
    public class Vertebrate : Animal
    {
        public override sealed int Eyes { get { return 2; } set{/*Force only 2 eyes, ignore setter*/} }
    }
    
    public class Bird : Vertebrate 
    {
        /// <summary>Not all birds can fly: penguins, emus and ostriches are some examples </summary> 
        public bool CanFly { get;set; }
    
        public override string ToString()
        {
            return base.ToString() + $", CanFly :{CanFly}";
        }
    }
    
    public class Fish : Vertebrate 
    {
        /// <summary>Fun fact, not all fish can swim backwards! Sharks is one example</summary>
        public bool CanSwimBackwards { get;set; }
    
        public override string ToString()
        {
            return base.ToString() + $", CanSwimBackwards :{CanSwimBackwards}";
        }
    }
    

    What we've shown here is a simple inheritance model that shows different properties being added to the base class we can use these to add some Animals to a list:

    List<Animal> myFavouriteAnimals = new List<Animal>();
    myFavouriteAnimals.Add(new Animal { Name = "Worm", Eyes = 0 });
    myFavouriteAnimals.Add(new Bird { Name = "Hawk", CanFly = true; });
    myFavouriteAnimals.Add(new Bird { Name = "Penguin", CanFly = false; });
    myFavouriteAnimals.Add(new Fish { Name = "Eel", CanSwimBackwards = true; });
    myFavouriteAnimals.Add(new Fish { Name = "Shark", CanSwimBackwards = false; });
    
    foreach(var animal in myFavouriteAnimals)
    {
        Console.WriteLine(animal.ToString());
    }
    

    This would produce the following result:
    Try it out here: https://dotnetfiddle.net/n6jgHO

    Worm - Eyes:0
    Hawk - Eyes:2, CanFly: True
    Penguin - Eyes:2, CanFly: False
    Eel - Eyes:2, CanSwimBackwards: True
    Shark - Eyes:2, CanSwimBackwards: False
    

    There's a little bit of syntactic sugar in that example but hopefully it helps to explain better scenarios for using inheritance other than just changing the value of an attribute defined in the base class.