Hands-On Design Patterns with C# and .NET Core

4.2 (6 reviews total)
By Gaurav Aroraa , Jeffrey Chilberto
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Overview of OOP in .NET Core and C#

About this book

Design patterns are essentially reusable solutions to common programming problems. When used correctly, they meet crucial software requirements with ease and reduce costs. This book will uncover effective ways to use design patterns and demonstrate their implementation with executable code specific to both C# and .NET Core.

Hands-On Design Patterns with C# and .NET Core begins with an overview of object-oriented programming (OOP) and SOLID principles. It provides an in-depth explanation of the Gang of Four (GoF) design patterns, including creational, structural, and behavioral. The book then takes you through functional, reactive, and concurrent patterns, helping you write better code with streams, threads, and coroutines. Toward the end of the book, you’ll learn about the latest trends in architecture, exploring design patterns for microservices, serverless, and cloud native applications. You’ll even understand the considerations that need to be taken into account when choosing between different architectures such as microservices and MVC.

By the end of the book, you will be able to write efficient and clear code and be comfortable working on scalable and maintainable projects of any size.

Publication date:
July 2019
Publisher
Packt
Pages
410
ISBN
9781789133646

 

Overview of OOP in .NET Core and C#

For over 20 years, the most popular programming languages have been based on the principles of object-oriented programming (OOP). The rise in popularity of OOP languages is largely to do with the benefits of being able to abstract complex logic into a structure, called an object, that can be more easily explained, and, more importantly, reused within an application. In essence, OOP is a software design approach, that is, a pattern for developing software using the concept of objects that contain data and functionality. As the software industry matured, patterns appeared in OOP for commonly occurring problems, as they were effective in solving the same problems but across different contexts and industries. As software moved from the mainframe to client servers and then to the cloud, additional patterns have emerged to help in reducing development costs and improving reliability. This book will explore design patterns, from the foundation of OOP to the architecture design patterns for cloud-based software.

OOP is based on the concept of an object. This object generally contains data, referred to as properties and fields, and code or behavior referred to as methods.

Design patterns are solutions to general problems that software programmers face during development, and are built from the experience of what works and what doesn't. These solutions are trialed and tested by numerous developers in various situations. The benefits of using a pattern based on this previous activity ensure that the same efforts are not repeated again and again. In addition to this, using a pattern adds a sense of reliability that the problem will be solved without introducing a defect or issue.

This chapter reviews OOP and how it applies to C#. Note that this is simply intended as a brief introduction and it is not meant to be a complete primer for OOP or C#; instead, the chapter will cover aspects of both in enough detail to introduce you to the design patterns that will be covered in subsequent chapters. This chapter will cover the following topics:

  • A discussion of OOP and how classes and objects work
  • Inheritance
  • Encapsulation
  • Polymorphism
 

Technical requirements

This chapter contains various code examples to explain these concepts. The code is kept simple and is just for demonstration purposes. Most of the examples involve a .NET Core console application written in C#.

To run and execute the code, you will need the following:

  • Visual Studio 2019 (you can also run the application using Visual Studio 2017 version 3 or later)
  • .NET Core
  • SQL Server (the Express Edition is used in this chapter)

Installing Visual Studio

In order to run these code examples, you will need to install Visual Studio or later (you can also use your preferred IDE). To do this, follow these instructions:

  1. Download Visual Studio from the following link: https://docs.microsoft.com/en-us/visualstudio/install/install-visual-studio.
  2. Follow the installation instructions included in the link. Multiple versions of Visual Studio are available; in this chapter, we are using Visual Studio for Windows.

Setting up .NET Core

If you do not have .NET Core installed, you will need to follow these instructions:

  1. Download .NET Core from the following link: https://www.microsoft.com/net/download/windows.
  2. Follow the installation instructions in the related library: https://dotnet.microsoft.com/download/dotnet-core/2.2.
The complete source code is available in GitHub. The source code shown in the chapter might not be complete, so it is recommended that you retrieve the source code in order to run the examples (https://github.com/PacktPublishing/Hands-On-Design-Patterns-with-C-and-.NET-Core/tree/master/Chapter1).

The models used in this book

As a learning aid, this book will contain many code samples in C# alongside diagrams and images to help describe specific concepts where possible. This is not a Unified Modeling Language (UML) book; however, for those with a knowledge of UML, many of the diagrams should seem familiar. This section provides a description of the class diagrams that will be used in this book.

Here, a class will be defined as including both fields and methods separated by a dashed line. If important to the discussion, then accessibility will be indicated as - for private, + for public, # for protected, and ~ for internal. The following screenshot illustrates this by showing a Car class with a private _name variable and a public GetName() method:

When showing relationships between objects, an association is shown with a solid line, an aggregation is shown with an open diamond, and a composition is shown with a filled diamond. When important to the discussion, multiplicity will be shown next to the class in question. The following diagram illustrates the Car class as having a single Owner, and up to three Passengers; it consists of four Wheels:

Inheritance is shown using an open triangle on the base class using a solid line. The following diagram shows the relationship between an Account base class and the CheckingAccount and SavingsAccount child classes: 

Interfaces are shown in a similar manner to inheritance, but they use a dashed line as well as an additional <<interface>> label, as shown in the following diagram:

This section provides an overview of the models used in this book. This style/approach was chosen because, hopefully, it will be familiar to the majority of readers. 

 

OOP and how classes and objects work

OOP refers to a software programming approach that uses objects defined as classes. These definitions include fields, sometimes called attributes, to store data and methods in order to provide functionality. The first OOP language was a simulation of real systems known as Simula (https://en.wikipedia.org/wiki/Simula) and was developed at the Norwegian Computing Center in 1960. The first pure OOP language came into existence in 1970 as the Smalltalk (https://en.wikipedia.org/wiki/Smalltalk) language. This language was designed to program the Dynabook (http://history-computer.com/ModernComputer/Personal/Dynabook.html), which is a personal computer created by Alan Kay. Several OOP languages evolved from there, with the most popular being Java, C++, Python, and C#. 

OOP is based on objects that contain data. The OOP paradigm allows developers to arrange/organize code into an abstract or logical structure called an object. An object can contain both data and behavior.

With the use of the OOP approach, we are doing the following:

  • Modularizing: Here, an application is decomposed into different modules.
  • Reusing the software: Here, we rebuild or compose an application from different (that is, existing or new) modules.

In the following sections, we will discuss and understand the concepts of OOP in more detail.

Explaining OOP

Earlier, programming approaches had limitations and they often became difficult to maintain. OOP offered a new paradigm in software development that had advantages over other approaches. The concept of organizing code into objects is not difficult to explain and this is a huge advantage for the adoption of a new pattern. Many examples can be taken from the real world in order to explain the concept. Complex systems can also be described using smaller building blocks (that is, an objects). These allow developers to look at sections of the solution individually while understanding how they fit into the entire solution.

With this in mind, let's define a program as follows:

"A program is a list of instructions that instructs the language compiler on what to do."

As you can see, an object is a way of organizing a list of instructions in a logical manner. Going back to the example of the house, the architect's instructions help us to build a house, but they are not the house itself. Instead the architect's instructions are an abstract representation of a house. A class is similar as it defines the features of an object. An object is then created from the definition of a class. This is often called instantiating the object.

To understand OOP more closely, we should mention two other significant programming approaches:

  • Structured programming: This is a term coined by Edsger W. Dijkstra in 1966. Structured programming is a programming paradigm that solves a problem to handle 1,000 lines of code and divides these into small parts. These small parts are mostly called subroutines, block structures, for and while loops, and more. Languages that use structured programming techniques include ALGOL, Pascal, PL/I, and more.
  • Procedural programming: This is a paradigm derived from structured programming and is simply based on how we make a call (also known as a procedural call). Languages that use procedural programming techniques include COBOL, Pascal, and C. A recent example of the Go programming language was published in 2009.
Procedural calls
A procedural call is where a collection of statements, known as a procedure, is activated. This is sometimes referred to as a procedure that is invoked

The main problem with these two approaches is that programs are not easily manageable once they grow. Programs with more complex and larger code bases strain these two approaches, leading to difficult-to-understand and difficult-to-maintain applications. To overcome such problems, OOP provides the following features:

  • Inheritance
  • Encapsulation
  • Polymorphism

In the following sections, we will discuss these features in more detail.

Inheritance, encapsulation, and polymorphism are sometimes referred to as the three pillars of OOP.

Before we begin, let's discuss some structures that are found in OOP.

A class

A class is a group or template definition of the methods and variables that describe an object. In other words, a class is a blueprint, containing the definition of the variables and the methods that are common to all instances of the class called objects.

Let's take a look at the following code example:

public class PetAnimal
{
private readonly string PetName;
private readonly PetColor PetColor;

public PetAnimal(string petName, PetColor petColor)
{
PetName = petName;
PetColor = petColor;
}

public string MyPet() => $"My pet is {PetName} and its color is {PetColor}.";
}

In the preceding code, we have a PetAnimal class that has two private fields called PetName and PetColor, and one method called MyPet().

An object

In the real world, objects share two characteristics, that is, state and behavior. In other words, we can say that every object has a name, color, and more; these characteristics are simply the state of an object. Let's take the example of any type of pet: a dog and a cat will both have a name by which they called. So, in this way, my dog is named Ace and my cat is named Clementine. Similarly, dogs and cats have specific behaviors, for example, dogs barks and cats meow.

In the Explaining OOP section, we discussed that OOP is a programming model that is supposed to combine a state or structure (data) and the behavior (method) to deliver software functionality. In the previous example, the different states of pets make up the actual data, while the behavior of the pets is the method. 

An object stores the information (which is simply data) in attributes and discloses its behavior through methods.

In terms of an OOP language such as C#, an object is an instance of a class. In our previous example, the real-world object, Dog, would be an object of the PetAnimal class.

Objects can be concrete (that is, a real-world object, such as a dog or cat, or any type of file, such as physical file or a computer file) or they can be conceptual, such as database schemas or code blueprints.

The following code snippet shows how an object contains data and a method, and how you can use it:

namespace OOPExample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("OOP example");
PetAnimal dog = new PetAnimal("Ace", PetColor.Black);
Console.WriteLine(dog.MyPet());
Console.ReadLine();
PetAnimal cat = new PetAnimal("Clementine", PetColor.Brown);
Console.WriteLine(cat.MyPet());
Console.ReadLine();
}
}
}

In the previous code snippet, we have created two objects: dog and cat. These objects are two different instances of a PetAnimal class. You can see that the fields or properties that contain data about the animal are given values using the constructor method. The constructor method is a special method used to create an instance of the class.

Let's visualize this example in the following diagram:

The preceding diagram is a pictorial representation of our previous code example, where we created two different Dog and Cat objects of the PetAnimal class. The diagram is relatively self-explanatory; it tells us that the object of Dog class is an instance of the PetAnimal class, as is the Cat object.

An interface

In C#, an interface defines what an object contains, or its contract; in particular, the methods, properties, events, or indices of the object. However, the interface does not provide implementation. Interfaces cannot contain attributes. This is in contrast to a base class, where the base class provides both the contract and the implementation. A class that implements an interface must implement everything specified in the interface. 

An abstract class
An abstract class is a bit of a hybrid between the interface and base class as it provides both implementations and attributes as well as methods that must be defined in the child classes.

Signature
The term signature can also be used to describe the contract of an object. 

 

Inheritance

One of the most important concepts in OOP is inheritance. Inheritance between classes allows us to define an is-a-type-of relationship; for example, a car is a type of vehicle. The importance of this concept is that it allows for objects of the same type to share similar features. Let's say that we have a system for managing different products of an online bookstore. We might have one class for storing information about a physical book and a different one for storing information about a digital or online book. The features that are similar between the two, such as the name, publisher, and author, could be stored in another class. Both the physical and digital book classes could then inherit from the other class. 

There are different terms to describe classes in inheritance: a child or derived class inherits from another class while the class being inherited from can be called the parent or base class. 

In the following sections, we will discuss inheritance in more detail.

Types of inheritance

Inheritance helps us to define a child class. This child class inherits the behavior of the parent or base class.

In C#, inheritance is symbolically defined using a colon (:).

Let's take a look at the different types of inheritance:

  • Single inheritance: As the most common type of inheritance, single inheritance describes a single class that is is derived from another class. 

Let's revisit the PetAnimal class previously mentioned and, instead, use inheritance to define our Dog and Cat classes. With inheritance, we can define some attributes that are common for both. For example, the name of the pet and the color of the pet would be common, so they would be located in a base class. The specifics of a cat or a dog would then be defined in a specific class; for example, the sound the cat and dog make. The following diagram illustrates a PetAnimal base class with two child classes: 

C# only supports single inheritance.
  • Multiple inheritance: Multiple inheritance happens when a derived class inherits multiple base classes. Languages such as C++ support multiple inheritance. C# does not support multiple inheritance, but we can achieve behaviors similar to multiple inheritance with the help of interfaces.
You can refer to the following post for more information about C# and multiple inheritance:
https://blogs.msdn.microsoft.com/csharpfaq/2004/03/07/why-doesnt-c-supportmultiple-inheritance/.
  • Hierarchical inheritance: Hierarchical inheritance happens when more than one class inherits from another class.
  • Multilevel inheritance: When a class is derived from a class that is already a derived class, it is called multilevel inheritance.
  • Hybrid inheritance: Hybrid inheritance is a combination of more than one inheritance.
C# does not support hybrid inheritance.
  • Implicit inheritance: All the types in .NET Core implicitly inherit from the System.Object class and its derived classes.
 

Encapsulation

Encapsulation is another fundamental concept in OOP where the details of a class, that is, the attributes and methods, can be visible or not visible outside the object. With encapsulation, a developer is providing guidance on how a class should be used as well as helping to prevent a class from being handled incorrectly. For example, let's say we wanted to only allow adding PetAnimal objects by using the AddPet(PetAnimal) method. We would do this by having the PetOwner class's AddPet(PetAnimal) method available while having the Pets attribute restricted to anything outside the PetAnimal class. In C#, this is possible by making the Pets attribute private. One reason for doing this would be if additional logic was required whenever a PetAnimal class was added, such as logging or validating that the PetOwner class could have a pet.

C# supports different levels of access that can be set on an item. An item could be a class, a class's attribute or method, or an enumeration:

  • Public: This indicates that access is available outside the item.
  • Private: This indicates that only the object has access to the item.
  • Protected: This indicates that only the object (and objects of classes that extended the class) can access the attribute or method.
  • Internal: This indicates that only objects within the same assembly have access to the item.
  • Protected Internal: This indicates that only the object (and objects of classes that extended the class) can access the attribute or method within the same assembly.

In the following diagram, access modifiers have been applied to PetAnimal:

As an example, the name of the pet and the color were made private to prevent access from outside the PetAnimal class. In this example, we are restricting the PetName and PetColor properties so only the PetAnimal class can access them in order to ensure that only the base class, PetAnimal, can change their values. The constructor of PetAnimal was protected to ensure only a child class could access it. In this application, only classes within the same library as the Dog class have access to the RegisterInObedienceSchool() method.

 

Polymorphism

The ability to handle different objects using the same interface is called polymorphism. This provides developers with the ability to build flexibility into applications by writing a single piece of functionality that can be applied to different forms as long as they share a common interface. There are different definitions of polymorphism in OOP, and we will distinguish between two main types:

  • Static or early binding: This form of polymorphism happens when the application is compiled.
  • Dynamic or late binding: This form of polymorphism happens when the application is running.

Static polymorphism

Static or early binding polymorphism happens at compile time and it primarily consists of method overloading, where a class has multiple methods with the same name but with different parameters. This is often useful to convey a meaning behind the method or to simplify the code. For example, in a calculator, it is more readable to have multiple methods for adding different types of number rather than having a different method name for each scenario; let's compare the following code:

int Add(int a, int b) => a + b;
float Add(float a, float b) => a + b;
decimal Add(decimal a, decimal b) => a + b;

In the following, the code is shown again with the same functionality, but without overloading the Add() method:

int AddTwoIntegers(int a, int b) => a + b;
float AddTwoFloats(float a, float b) => a + b;
decimal AddTwoDecimals(decimal a, decimal b) => a + b;

In the pet example, an owner will use different food to feed objects of the cat and dog class. We can define this as the PetOwner class with two methods for Feed(), as follows:

public void Feed(PetDog dog)
{
PetFeeder.FeedPet(dog, new Kibble());
}

public void Feed(PetCat cat)
{
PetFeeder.FeedPet(cat, new Fish());
}

Both methods use a PetFeeder class to feed the pet, while the dog class is given Kibble and the cat instance is given Fish. The PetFeeder class is described in the Generics section.

Dynamic polymorphism

Dynamic or late binding polymorphism happens while the application is running. There are multiple situations where this can occur and we'll cover three common forms in C#: interface, inheritance, and generics.

Interface polymorphism

An interface defines the signature that a class must implement. In the PetAnimal example, imagine that we define pet food as providing an amount of energy, as follows:

public interface IPetFood
{
int Energy { get; }
}

By itself, the interface cannot be instantiated but describes what an instance of IPetFood must implement. For example, Kibble and Fish might provide different levels of energy, as shown in the following code:

public class Kibble : IPetFood
{
public int Energy => 7;
}

public class Fish : IPetFood
{
int IPetFood.Energy => 8;
}

In the preceding code snippet, Kibble provides less energy than Fish.

Inheritance polymorphism

Inheritance polymorphism allows for functionality to be determined at runtime in a similar way to an interface but applies to class inheritance. In our example, a pet can be fed, so we can define this as having a new Feed(IPetFood) method, which uses the interface that was defined previously:

public virtual void Feed(IPetFood food)
{
Eat(food);
}

protected void Eat(IPetFood food)
{
_hunger -= food.Energy;
}

The preceding code indicates that all implementations of PetAnimal will have a Feed(IPetFood) method and child classes can provide a different implementation. Eat(IPetFood food) is not marked as virtual, as it is intended that all PetAnimal objects will use the method without needing to override its behavior. It is also marked as protected to prevent it being accessed from outside the object.

A virtual method does not have to be defined in a child class; this differs from an interface, where all methods in an interface must be implemented.

PetDog will not override the behavior of the base class as a dog will eat both Kibble and Fish. A cat is more discerning, as shown in the following code:

public override void Feed(IPetFood food)
{
if (food is Fish)
{
Eat(food);
}
else
{
Meow();
}
}

Using the override keyword, PetCat will change the behavior of the base class, resulting in a cat only eating fish.

Generics

A generic defines a behavior that can be applied to a class. A commonly used form of this is in collections, where the same approach to handling an object can be applied regardless of the type of object. For example, a list of strings or a list of integers can be handled using the same logic without having to differentiate between the specific types.

Going back to pets, we could define a generic class for feeding a pet. This class simply feeds a pet given both the pet and some food, as shown in the following code:

public static class PetFeeder
{
public static void FeedPet<TP, TF>(TP pet, TF food) where TP : PetAnimal
where TF : IPetFood
{
pet.Feed(food);
}
}

There are a couple of interesting thing to point out here. First of all, the class does not have to be instantiated as both the class and method are marked as static. The generic method is described using the method signature, FeedPet<TP, TF>. The where keyword is used to indicate additional requirements as to what TP and TF must be. In this example, the where keyword defines TP as having to be a type of PetAnimal, while TF must implement the IPetFood interface.

 

Summary

In this chapter, we discussed OOP and its three main features: inheritance, encapsulation, and polymorphism. Using these features, the classes within an application can be abstracted to provide definitions that are both easy to understand and protected against being used in a manner that is inconsistent with its purpose. This is an essential difference between OOP and some earlier types of software development language such as structural and procedural programming. With the ability to abstract functionality, the ability to reuse and maintain code is increased.

In the next chapter, we will discuss various patterns used in enterprise software development. We will cover programming patterns as well as software development principles and patterns used in the Software Development Life Cycle (SDLC).

 

Questions

The following questions will allow you to consolidate the information contained in this chapter:

  1. What do the terms late and early binding refer to?
  2. Does C# support multiple inheritance?
  3. In C#, what level of encapsulation could be used to prevent access to a class from outside the library?
  4. What is the difference between aggregation and composition? 
  5. Can interfaces contain properties? (This is a bit of a trick question.)
  6. Do dogs eat fish?

About the Authors

  • Gaurav Aroraa

    Gaurav Aroraa is a serial entrepreneur and start-up mentor. He has done an M.Phil in computer science. He is a Microsoft MVP award recipient. He is a lifetime member of the Computer Society of India (CSI), is an advisory member and senior mentor at IndiaMentor, is certified as a Scrum trainer/coach, is ITIL-F certified, and is PRINCE-F and PRINCE-P certified. He is an open source developer and a contributor to the Microsoft TechNet community. Recently, Gaurav was awarded "Icon of the year – excellence in Mentoring Technology Startups" for 2018-19 by Radio City, a Jagran initiative, for his extraordinary work during his 22-year career in the industry in the field of technology mentoring.

    Browse publications by this author
  • Jeffrey Chilberto

    Jeffrey is a software consultant specializing in the Microsoft technical stack including Azure, BizTalk, ASP.Net, MVC, WCF and SQL Server with experience in a wide range of industries including banking, telecommunications and health care in the United States, Europe, Australia and New Zealand.

    Browse publications by this author

Latest Reviews

(6 reviews total)
I thought the eBook was organized in a very good way. I've already used one of the design patterns I learned in the book at my job.
Perfecta para iniciar en estos temas
The authors demonstrate that they don't really understand the subject they're writing about.

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial