You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
6.9 KiB
Markdown
246 lines
6.9 KiB
Markdown
---
|
|
marp: true
|
|
paginate: true
|
|
math: mathjax
|
|
theme: buutti
|
|
title: 15. Design Patterns in C#
|
|
---
|
|
|
|
# Design Patterns in C#
|
|
|
|
<!-- headingDivider: 5 -->
|
|
<!-- class: invert -->
|
|
|
|
## Overview
|
|
|
|
* The Singleton Pattern
|
|
* Reflection and attributes
|
|
* Dependency Injection
|
|
|
|
## The Singleton Pattern
|
|
|
|
### The problem
|
|
|
|
* In most cases, it makes no sense to create an instance of a class every time its members need to be accessed
|
|
* For example, a shared resource manager that is being called from multiple classes
|
|
* While a static class could be used for this, there are some problems:
|
|
* As stated in [lecture 10](10-static-members-methods-and-classes#StaticClasses), static classes can only have static members
|
|
* Static classes cannot be instantiated, so a reference to them cannot be passed around as a parameter
|
|
* Static classes cannot inherit from other classes or implement interfaces
|
|
* [And many more... ](https://www.c-sharpcorner.com/UploadFile/akkiraju/singleton-vs-static-classes/)
|
|
|
|
### The solution
|
|
|
|
* The __singleton__ class is a class that benefits from all the perks of a non-static class (non-static members, inheritance, referencing…), but only one (or zero) instances of it ever exists during the lifetime of your application
|
|
* For example, reading from / writing to a file that should be accessible to multiple clients, should be made into a singleton
|
|
* Instead of every client directly accessing the same file (and possibly causing massive performance issues), the singleton is instantiated once and a reference to it is provided to clients
|
|
* The singleton could take care of queueing the read/write requests and be the only entity accessing the actual file
|
|
|
|
---
|
|
|
|
A singleton implementation could look something like this:
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
class Singleton
|
|
{
|
|
private static Singleton instance = null;
|
|
|
|
private Singleton() { }
|
|
public void MySingletonFunction()
|
|
{
|
|
Console.WriteLine
|
|
("This function is accessible anywhere!");
|
|
}
|
|
|
|
public static Singleton Instance
|
|
{
|
|
get
|
|
{
|
|
if (instance == null)
|
|
instance = new Singleton();
|
|
return instance;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
Singleton.Instance.MySingletonFunction();
|
|
// Outputs: "This function is accessible
|
|
// from everywhere!"
|
|
}
|
|
}
|
|
```
|
|
</div>
|
|
</div>
|
|
|
|
### Implementing a singleton pattern
|
|
|
|
* The exact implementation of the singleton is out of the scope of this course, but it is important to understand that it exists and what its purpose is
|
|
* Multitude of examples for different use cases are available and can be found by googling
|
|
|
|
## Reflection
|
|
|
|
* Reflective programming or reflection is the ability for the program to examine or modify its own structure and behaviour
|
|
* C# has special reflection methods that we can use to get information about types
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* Simple reflection example to obtain type information:
|
|
```csharp
|
|
int i = 42;
|
|
Type type = i.GetType();
|
|
Console.WriteLine(type);
|
|
```
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
* Use strings to invoke methods:
|
|
```csharp
|
|
// Without reflection
|
|
var foo = new Foo();
|
|
foo.PrintHello();
|
|
|
|
// With reflection
|
|
Object foo = Activator.CreateInstance
|
|
("complete.classpath.and.Foo");
|
|
MethodInfo method = foo
|
|
.GetType()
|
|
.GetMethod("PrintHello");
|
|
method.Invoke(foo, null);
|
|
```
|
|
|
|
### Attributes
|
|
|
|
* [Attributes](https://learn.microsoft.com/en-us/dotnet/standard/attributes/
|
|
) can be used to extend methods, classes, or even entire programs with new ***metadata***
|
|
* Metadata is information about the types defined
|
|
* Attributes use reflection so the program can examine its own metadata
|
|
* Here's an example of the built-in [SerializableAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.serializableattribute?view=net-9.0).
|
|
* Attribute is used by placing its name in square brackets `[]` above the declaration of the entity you want it to apply to
|
|
```csharp
|
|
[Serializable]
|
|
public class SampleClass
|
|
{
|
|
// Objects of this type can be serialized.
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
* Attributes can also have parameters:
|
|
```csharp
|
|
[Obsolete("Will be removed in next version.")]
|
|
public static int Add(int a, int b)
|
|
{
|
|
return (a + b);
|
|
}
|
|
```
|
|
|
|
* You can add multiple attributes either on separate lines or by separating them with a comma:
|
|
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
[Conditional("DEBUG")]
|
|
[Conditional("TEST1")]
|
|
void TraceMethod()
|
|
{
|
|
// ...
|
|
}
|
|
```
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
[Conditional("DEBUG"), Conditional("TEST1")]
|
|
void TraceMethod()
|
|
{
|
|
// ...
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Custom attributes
|
|
|
|
* You can write your own [custom attributes](https://learn.microsoft.com/en-us/dotnet/standard/attributes/writing-custom-attributes) by inheriting from the `Attribute` class
|
|
```csharp
|
|
// This defaults to Inherited = true.
|
|
public class MyAttribute : Attribute
|
|
{
|
|
//...
|
|
}
|
|
```
|
|
* Then, you can use it like any other attribute
|
|
```csharp
|
|
public class MyClass
|
|
{
|
|
[MyAttribute]
|
|
public virtual void MyMethod()
|
|
{
|
|
//...
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dependency Injection
|
|
|
|
### The problem
|
|
|
|
* Traditionally, when new objects of classes are instantiated, the consuming class handles the creation of the objects
|
|
* Many classes change their functionality throughout the development of any project
|
|
* This means that also _every single_ consuming class has to change
|
|
* This is called *__tight coupling__*
|
|
|
|
### The solution
|
|
|
|
* What if, instead of directly creating the objects, they were provided by some interface that takes care of the creation?
|
|
* This way, even if the base class changes, the consuming classes won't care because they only know about the provider
|
|
* This provider is called *__Container__*, and the functionality being injected is called *__Service__*
|
|
* In ASP.NET, this container system is built in
|
|
|
|
### Dependency injection in ASP.NET
|
|
|
|
```csharp
|
|
public class HomeController : Controller
|
|
{
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
public HomeController(IUserRepository userRepository)
|
|
{
|
|
_userRepository = userRepository;
|
|
}
|
|
// User repository including all users is now accessible in HomeController
|
|
}
|
|
```
|
|
|
|
## Design Patterns
|
|
|
|
* If the concepts of a singleton and dependency injection flew over your head, don't worry about it
|
|
* The important thing is to know they exist so that when they come up again in ASP.NET, you have already familiarized yourself with the terms
|
|
* Thus, understanding the logic behind ASP.NET becomes less overwhelming
|
|
* There are many more design patterns, see the material [here](https://www.c-sharpcorner.com/UploadFile/bd5be5/design-patterns-in-net/)
|
|
```csharp
|
|
public void ConfigureServices(IServiceCollection services)
|
|
{
|
|
services.AddSingleton<IDateTime, SystemDateTime>();
|
|
services.AddControllersWithViews();
|
|
}
|
|
```
|