Add code examples, and documentation
This commit is contained in:
7
.csharpierrc
Normal file
7
.csharpierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"useTabs": false,
|
||||
"indentSize": 3,
|
||||
"endOfLine": "lf",
|
||||
"xmlWhitespaceSensitivity": "strict"
|
||||
}
|
||||
484
.gitignore
vendored
Normal file
484
.gitignore
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from `dotnet new gitignore`
|
||||
|
||||
# dotenv files
|
||||
.env
|
||||
|
||||
*.pdf
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
.idea/
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Vim temporary swap files
|
||||
*.swp
|
||||
29
DesignPatterns.slnx
Normal file
29
DesignPatterns.slnx
Normal file
@@ -0,0 +1,29 @@
|
||||
<Solution>
|
||||
<Folder Name="/patterns/" />
|
||||
<Folder Name="/patterns/bridge/">
|
||||
<Project Path="patterns/bridge/Bridge Example/Bridge Example.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/decorator/">
|
||||
<Project Path="patterns/decorator/IceCreamDecorations/IceCreamDecorations.csproj" />
|
||||
<Project Path="patterns/decorator/ShapeDecorations/ShapeDecorations.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/facade/">
|
||||
<Project Path="patterns/facade/CarEngineFacade/CarEngineFacade.csproj" />
|
||||
<Project Path="patterns/facade/CarEngineFacadeRecording/CarEngineFacadeRecording.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/mediator/">
|
||||
<Project Path="patterns/mediator/AirTrafficControl/AirTrafficControl.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/memento/">
|
||||
<Project Path="patterns/memento/MementoPattern/MementoPattern.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/observer/">
|
||||
<Project Path="patterns/observer/ObserverPattern/UpdatedOne.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/patterns/state/">
|
||||
<Project Path="patterns/state/BadCarState/BadCarState.csproj" />
|
||||
<Project Path="patterns/state/CarState/CarState.csproj" />
|
||||
<Project Path="patterns/state/WithoutStatePattern/WithoutStatePattern.csproj" />
|
||||
<Project Path="patterns/state/WithStatePattern/WithStatePattern.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
84
docs/README.md
Normal file
84
docs/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Design Patterns Cheatsheet
|
||||
|
||||
This repo implements seven patterns in C#, cross-referenced with _Design
|
||||
Patterns: Elements of Reusable Object-Oriented Software_ (Gamma / Helm / Johnson
|
||||
/ Vlissides — the "Gang of Four", **GoF**) and _Dive Into Design Patterns_
|
||||
(Alexander Shvets, 2019).
|
||||
|
||||
Every code sample comes from the actual project files. Each **Output** block
|
||||
reflects a real build and run of that project — none of the output is
|
||||
hand-traced.
|
||||
|
||||
## The patterns
|
||||
|
||||
**Structural** — how to assemble objects and classes into larger structures.
|
||||
|
||||
| Pattern | One-line idea | Repo project |
|
||||
| ------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| [Bridge](bridge.md) | Splits one exploding hierarchy into two, joined by composition. | `patterns/bridge/Bridge Example/` |
|
||||
| [Decorator](decorator.md) | Stacks wrappers, each adding one behavior, all sharing an interface. | `patterns/decorator/ShapeDecorations/` |
|
||||
| [Facade](facade.md) | Hides a pile of fiddly subsystem classes behind one friendly class. | `patterns/facade/CarEngineFacade/` (+ `CarEngineFacadeRecording/`) |
|
||||
|
||||
**Behavioral** — how objects communicate and divide responsibility.
|
||||
|
||||
| Pattern | One-line idea | Repo project |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| [Mediator](mediator.md) | Stops objects from calling each other directly; routes every call through one go-between. | `patterns/mediator/AirTrafficControl/` |
|
||||
| [Memento](memento.md) | Lets an object snapshot itself, opaquely, for undo. | `patterns/memento/MementoPattern/` |
|
||||
| [Observer](observer.md) | Publishes changes to a dynamic list of subscribed listeners. | `patterns/observer/ObserverPattern/` (+ `UpdatedOne/`) |
|
||||
| [State](state.md) | Replaces a giant `switch` on a state field with one class per state. | `patterns/state/BadCarState/` + `CarState/` (+ `WithStatePattern/`, `WithoutStatePattern/`) |
|
||||
|
||||
## Also in here
|
||||
|
||||
- **[Cross-pattern comparisons](cross-pattern-comparisons.md)** — the
|
||||
distinctions most likely to be tested and most likely to cause confusion in
|
||||
review, such as Bridge vs. State vs. Strategy, Decorator vs. Proxy vs.
|
||||
Adapter, and Facade vs. Mediator vs. Observer.
|
||||
- **[Principles these examples keep demonstrating](principles.md)** —
|
||||
encapsulate what varies, program to an interface, favor composition, the
|
||||
Single Responsibility Principle, and the Open/Closed Principle.
|
||||
|
||||
## Quick reference: GoF intents (verbatim)
|
||||
|
||||
| Pattern | GoF Intent | Key structural move |
|
||||
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
||||
| **Bridge** | "Decouple an abstraction from its implementation so that the two can vary independently." | Abstraction _has-a_ Implementor |
|
||||
| **Decorator** | "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality." | Wrapper _is-a_ AND _has-a_ Component |
|
||||
| **Facade** | "Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use." | Facade _has-many_ subsystem parts |
|
||||
| **Mediator** | "Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently." | N×N links → N links to a hub |
|
||||
| **Memento** | "Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later." | Nested private snapshot class |
|
||||
| **Observer** | "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically." | Subject holds `List<IObserver>` |
|
||||
| **State** | "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class." | Context delegates to a swappable State |
|
||||
|
||||
## Before / after pair
|
||||
|
||||
One pattern ships with a deliberately bad version alongside the patterned
|
||||
version. Diffing that pair teaches more than either version alone, so both are
|
||||
reproduced in full:
|
||||
|
||||
- **[State](state.md)** — `BadCarState` (a `switch` inside a `switch`, four
|
||||
times over) vs. `CarState` (one class per state). The two produce
|
||||
**byte-identical** output, confirming the refactor changed structure and
|
||||
nothing else.
|
||||
|
||||
## Run these projects
|
||||
|
||||
All projects target `net10.0` and are wired into the root `DesignPatterns.slnx`.
|
||||
Build and run any project directly with the .NET 10 SDK:
|
||||
|
||||
```bash
|
||||
dotnet run --project "patterns/bridge/Bridge Example/Bridge Example.csproj"
|
||||
```
|
||||
|
||||
`CarState`, `BadCarState`, `WithStatePattern`, `WithoutStatePattern`, and
|
||||
`MementoPattern` read from stdin. Pipe input to them:
|
||||
|
||||
```bash
|
||||
printf 'gas\nturn-clockwise\ngas\nbrake\nturn-counter\nexit\n' | dotnet run --project patterns/state/CarState/CarState.csproj
|
||||
```
|
||||
|
||||
Each `patterns/<pattern>/` folder also holds any in-class recording or
|
||||
before/after variant alongside the canonical example — for example,
|
||||
`patterns/facade/CarEngineFacadeRecording/`. The `exercises/` folder holds
|
||||
material that doesn't map to a specific GoF pattern, such as `ClassIntroExam`, a
|
||||
pass-by-value-vs-reference demo.
|
||||
205
docs/bridge.md
Normal file
205
docs/bridge.md
Normal file
@@ -0,0 +1,205 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Bridge
|
||||
|
||||
**Category:** Structural | **Source:** `patterns/bridge/Bridge Example/`
|
||||
|
||||
## Problem
|
||||
|
||||
A `Notification` hierarchy (`Alert`, `Reminder`) needs a second, independent
|
||||
dimension — delivery channel (`Email`, `SMS`). Subclassing both at once yields
|
||||
`EmailAlertNotification`, `SmsReminderNotification`, … and the count grows
|
||||
geometrically: each new notification type needs one subclass _per existing
|
||||
channel_, and each new channel needs one _per existing notification type_.
|
||||
|
||||
## Solution
|
||||
|
||||
Stop expressing both dimensions with inheritance. Keep one as a class hierarchy
|
||||
(the **Abstraction**), extract the other into its own hierarchy (the
|
||||
**Implementation**), and join them by composition — the abstraction holds a
|
||||
reference to an implementor and delegates the real work to it. Each hierarchy
|
||||
now grows independently, and the implementation is swappable at runtime.
|
||||
|
||||
> The GoF words "Abstraction" and "Implementation" do **not** mean your
|
||||
> language's `abstract`/`interface` keywords. They mean "the control-facing
|
||||
> half" and "the platform-facing half" of a split. (_Dive Into Design Patterns_,
|
||||
> p.165)
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Abstraction** → `Notification` — defines the client-facing interface, holds
|
||||
an `IMessageSender`.
|
||||
- **RefinedAbstraction** → `AlertNotification`, `ReminderNotification` —
|
||||
variants of control logic.
|
||||
- **Implementor** → `IMessageSender` — primitive operations; need not mirror the
|
||||
Abstraction's interface.
|
||||
- **ConcreteImplementor** → `EmailSender`, `SmsSender` — the channel-specific
|
||||
code.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Notification {
|
||||
<<abstract>>
|
||||
#IMessageSender _sender
|
||||
+Notify(message)*
|
||||
}
|
||||
class IMessageSender {
|
||||
<<interface>>
|
||||
+SendMessage(message)
|
||||
}
|
||||
Notification o--> IMessageSender : THE BRIDGE - delegates to
|
||||
Notification <|-- AlertNotification
|
||||
Notification <|-- ReminderNotification
|
||||
IMessageSender <|.. EmailSender
|
||||
IMessageSender <|.. SmsSender
|
||||
```
|
||||
|
||||
The single `o-->` arrow _is_ the bridge. Left of it, control logic grows freely;
|
||||
right of it, platforms grow freely; neither side multiplies the other.
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// IMessageSender.cs — Implementor
|
||||
public interface IMessageSender
|
||||
{
|
||||
void SendMessage(string message);
|
||||
}
|
||||
|
||||
// EmailSender.cs — ConcreteImplementor
|
||||
public class EmailSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"Email sent: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
// SmsSender.cs — ConcreteImplementor
|
||||
public class SmsSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"SMS sent: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Notification.cs — Abstraction
|
||||
public abstract class Notification
|
||||
{
|
||||
protected readonly IMessageSender _sender;
|
||||
|
||||
protected Notification(IMessageSender sender)
|
||||
{
|
||||
_sender = sender; // the bridge is established here, at construction
|
||||
}
|
||||
|
||||
public abstract void Notify(string message);
|
||||
}
|
||||
|
||||
// AlertNotification.cs — RefinedAbstraction (ReminderNotification is identical but for its prefix)
|
||||
public class AlertNotification : Notification
|
||||
{
|
||||
public AlertNotification(IMessageSender sender) : base(sender) { }
|
||||
|
||||
public override void Notify(string message)
|
||||
{
|
||||
_sender.SendMessage($"ALERT: {message}"); // high-level op built from the primitive
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Program.cs — the client picks both halves independently, at runtime
|
||||
Notification n1 = new AlertNotification(new EmailSender());
|
||||
Notification n2 = new AlertNotification(new SmsSender());
|
||||
Notification n3 = new ReminderNotification(new EmailSender());
|
||||
Notification n4 = new ReminderNotification(new SmsSender());
|
||||
|
||||
n1.Notify("Server is down");
|
||||
n2.Notify("Server is down");
|
||||
n3.Notify("Meeting at 3pm");
|
||||
n4.Notify("Meeting at 3pm");
|
||||
```
|
||||
|
||||
The payoff: at no point is there an `EmailAlertNotification` class or an
|
||||
`SmsReminderNotification` class — the notification type and the delivery channel
|
||||
bind together at runtime, not compile time. 2 abstractions × 2 implementors = 4
|
||||
usable combinations from **4** classes, not 4 combination classes. At 5 × 5 it
|
||||
would be 10 classes instead of 25.
|
||||
|
||||
## Output (verified)
|
||||
|
||||
```
|
||||
Email sent: ALERT: Server is down
|
||||
SMS sent: ALERT: Server is down
|
||||
Email sent: Reminder: Meeting at 3pm
|
||||
SMS sent: Reminder: Meeting at 3pm
|
||||
```
|
||||
|
||||
## GoF pseudocode (remote controls × devices)
|
||||
|
||||
```
|
||||
class RemoteControl is
|
||||
protected field device: Device
|
||||
constructor RemoteControl(device: Device) is this.device = device
|
||||
method togglePower() is
|
||||
if (device.isEnabled()) then device.disable() else device.enable()
|
||||
method volumeUp() is
|
||||
device.setVolume(device.getVolume() + 10) // high-level op from primitives
|
||||
|
||||
class AdvancedRemoteControl extends RemoteControl is
|
||||
method mute() is device.setVolume(0)
|
||||
|
||||
interface Device is
|
||||
method isEnabled() method enable() method disable()
|
||||
method getVolume() method setVolume(percent)
|
||||
|
||||
class Tv implements Device is // ...
|
||||
class Radio implements Device is // ...
|
||||
|
||||
tv = new Tv()
|
||||
remote = new RemoteControl(tv)
|
||||
remote.togglePower()
|
||||
```
|
||||
|
||||
## Applicability
|
||||
|
||||
- You want to avoid permanently binding an abstraction to one implementation —
|
||||
for example, when the implementation must be selected or switched at run-time.
|
||||
- Both abstraction and implementation should be extensible by subclassing,
|
||||
independently of each other.
|
||||
- Changes to an implementation must not force clients to recompile.
|
||||
- You already have a proliferation of combination classes (GoF calls this smell
|
||||
"nested generalizations").
|
||||
- You want to share one implementor among several abstraction objects, invisibly
|
||||
to the client.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Platform-independent classes; implementor swappable at runtime | Can over-complicate a class that was already small and cohesive |
|
||||
| Client only ever sees the high-level abstraction | |
|
||||
| Open/Closed — add abstractions or implementors independently | |
|
||||
| Single Responsibility — control logic vs. platform detail live apart | |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- ADO.NET — `DbConnection`/`DbCommand` as abstraction, per-provider drivers as
|
||||
implementors.
|
||||
- `ILogger` as an abstraction over pluggable logging providers.
|
||||
- Any `HttpMessageHandler` swapped into `HttpClient`.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Adapter** is retrofitted onto existing incompatible classes; Bridge is
|
||||
designed up front.
|
||||
- **Bridge / State / Strategy** share the "delegate to a composed object" shape
|
||||
but differ in intent — the name still communicates the problem being solved.
|
||||
- **Abstract Factory** pairs well when only certain abstraction/implementor
|
||||
combinations are legal.
|
||||
|
||||
---
|
||||
|
||||
[All patterns](README.md) · **Next →** [Decorator](decorator.md)
|
||||
20
docs/cross-pattern-comparisons.md
Normal file
20
docs/cross-pattern-comparisons.md
Normal file
@@ -0,0 +1,20 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Cross-pattern comparisons worth memorizing
|
||||
|
||||
These are the distinctions most likely to be tested, and the ones that actually
|
||||
bite in review:
|
||||
|
||||
| Confusion | The tell |
|
||||
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Bridge vs. State vs. Strategy** | Near-identical UML (context → interface, delegate). Bridge: two hierarchies designed up front, both varying. State: the delegate swaps _itself_ and drives transitions. Strategy: interchangeable, mutually-unaware algorithms chosen by the client. |
|
||||
| **Decorator vs. Proxy vs. Adapter** | All wrap one object. Adapter **changes** the interface. Decorator **keeps** it and adds behavior, client-controlled and stackable. Proxy **keeps** it but controls access/lifetime and manages its own subject. |
|
||||
| **Decorator vs. Composite** | Decorator has exactly one child and _adds_ responsibility; Composite has many and _aggregates_ their results. |
|
||||
| **Facade vs. Mediator** | Direction. Facade is one-way and the subsystem doesn't know it exists. Mediator is many-way and colleagues hold references to it. |
|
||||
| **Mediator vs. Observer** | Mediator removes mutual dependencies among a known set of components via a hub. Observer creates dynamic, independently-subscribable one-way links. Often combined: mediator-as-publisher. |
|
||||
| **Memento vs. Command** | Not rivals — partners. Command = "the operation"; Memento = "the state to restore if it's undone." |
|
||||
| **Facade vs. Abstract Factory** | Both hide a subsystem. Abstract Factory hides only _creation_; Facade hides creation **and** call sequencing. |
|
||||
|
||||
---
|
||||
|
||||
[All patterns](README.md)
|
||||
253
docs/decorator.md
Normal file
253
docs/decorator.md
Normal file
@@ -0,0 +1,253 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Decorator
|
||||
|
||||
**Category:** Structural | **Also known as: Wrapper** | **Source:**
|
||||
`patterns/decorator/ShapeDecorations/`
|
||||
|
||||
## Problem
|
||||
|
||||
You want to add behavior to _individual objects_ at runtime, not to every
|
||||
instance of a class, without an explosion of subclasses for every combination
|
||||
(`BorderedScrollableTextView`, `BorderedTextView`, …). Inheritance is also
|
||||
static: once an object is constructed you cannot add or remove an inherited
|
||||
behavior.
|
||||
|
||||
## Solution
|
||||
|
||||
Wrap the object in a decorator that **implements the same interface** and
|
||||
**holds a reference** to what it wraps. The decorator delegates to the wrappee
|
||||
and adds its own behavior before or after. Because a decorator is itself the
|
||||
component type, decorators wrap decorators — you build a stack, and the
|
||||
outermost object stays indistinguishable from the original to every caller.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Component** → `IShape`
|
||||
- **ConcreteComponent** → `Circle`, `Rectangle`
|
||||
- **Decorator** → `ShapeDecorator` (abstract base holding the wrappee)
|
||||
- **ConcreteDecorator** → `RedColor`, `BlueBorder`, `HugeSize`
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class IShape {
|
||||
<<interface>>
|
||||
+Draw()
|
||||
}
|
||||
class ShapeDecorator {
|
||||
<<abstract>>
|
||||
#IShape _decorated
|
||||
+Draw()*
|
||||
}
|
||||
IShape <|.. Circle
|
||||
IShape <|.. Rectangle
|
||||
IShape <|.. ShapeDecorator
|
||||
ShapeDecorator o--> IShape : wraps - recursive
|
||||
ShapeDecorator <|-- RedColor
|
||||
ShapeDecorator <|-- BlueBorder
|
||||
ShapeDecorator <|-- HugeSize
|
||||
```
|
||||
|
||||
`ShapeDecorator` both **implements** `IShape` and **holds** an `IShape`. That
|
||||
double relationship is the whole pattern — it is what makes wrapping recursive.
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// IShape.cs — Component
|
||||
public interface IShape
|
||||
{
|
||||
void Draw();
|
||||
}
|
||||
|
||||
// Circle.cs / Rectangle.cs — ConcreteComponents (the only classes that can start a stack)
|
||||
public class Circle : IShape
|
||||
{
|
||||
public void Draw() { Console.WriteLine("Drawing a circle"); }
|
||||
}
|
||||
|
||||
public class Rectangle : IShape
|
||||
{
|
||||
public void Draw() { Console.WriteLine("Drawing a Rectangle"); }
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ShapeDecorator.cs — base Decorator (doc comments condensed from the original)
|
||||
public abstract class ShapeDecorator : IShape
|
||||
{
|
||||
/// Maintains a reference to the shape which it decorates.
|
||||
/// Marked "protected" so that inheriting classes have access to it.
|
||||
protected IShape _decorated;
|
||||
|
||||
/// Accepts a reference to any IShape. Since the ShapeDecorator itself IS an
|
||||
/// IShape, this decorator can also decorate other decorators. However, since a
|
||||
/// ShapeDecorator has to be instantiated with an existing IShape, at some point
|
||||
/// in the chain there must be a concrete shape.
|
||||
public ShapeDecorator(IShape decorated)
|
||||
{
|
||||
_decorated = decorated;
|
||||
}
|
||||
|
||||
/// Abstract: satisfies IShape, but forces subclasses to write their own logic.
|
||||
public abstract void Draw();
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// RedColor.cs — ConcreteDecorator (BlueBorder and HugeSize are the same shape)
|
||||
public class RedColor : ShapeDecorator
|
||||
{
|
||||
public RedColor(IShape decorated) : base(decorated) { }
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
_decorated.Draw(); // 1. delegate inward
|
||||
Console.WriteLine("\tWhich is red in color"); // 2. then add own behavior
|
||||
}
|
||||
}
|
||||
|
||||
// BlueBorder.Draw() -> _decorated.Draw(); "\tWith a blue border"
|
||||
// HugeSize.Draw() -> _decorated.Draw(); "\tThat is enourmous in size"
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Program.cs — client assembles the stack; each variable remains an IShape
|
||||
var circle = new Circle();
|
||||
circle.Draw();
|
||||
|
||||
var red = new RedColor(circle); // Circle → RedColor
|
||||
red.Draw();
|
||||
|
||||
var blueBorder = new BlueBorder(red); // Circle → RedColor → BlueBorder
|
||||
blueBorder.Draw();
|
||||
|
||||
var huge = new HugeSize(blueBorder); // Circle → RedColor → BlueBorder → HugeSize
|
||||
huge.Draw();
|
||||
|
||||
// Second block: same decorators, different order, different concrete component
|
||||
var rect = new Rectangle();
|
||||
var blueBorder2 = new BlueBorder(rect);
|
||||
var huge2 = new HugeSize(blueBorder2);
|
||||
var red2 = new RedColor(huge2);
|
||||
red2.Draw();
|
||||
```
|
||||
|
||||
## Output (verified)
|
||||
|
||||
```
|
||||
Drawing a circle
|
||||
|
||||
Drawing a circle
|
||||
Which is red in color
|
||||
|
||||
Drawing a circle
|
||||
Which is red in color
|
||||
With a blue border
|
||||
|
||||
Drawing a circle
|
||||
Which is red in color
|
||||
With a blue border
|
||||
That is enourmous in size
|
||||
|
||||
Drawing a Rectangle
|
||||
|
||||
Drawing a Rectangle
|
||||
With a blue border
|
||||
|
||||
Drawing a Rectangle
|
||||
With a blue border
|
||||
That is enourmous in size
|
||||
|
||||
Drawing a Rectangle
|
||||
With a blue border
|
||||
That is enourmous in size
|
||||
Which is red in color
|
||||
```
|
||||
|
||||
Read the output as the call stack unwinding: because every decorator calls
|
||||
`_decorated.Draw()` **first**, the innermost concrete shape prints first and
|
||||
decorations print outward in wrapping order. The `Program.cs` comment says _"the
|
||||
order in which we decorate doesn't matter"_ — true for _these_ decorators,
|
||||
because each only appends an independent line. It is **not** true in general
|
||||
(see Cons).
|
||||
|
||||
## GoF pseudocode (compression + encryption over a data source)
|
||||
|
||||
```
|
||||
interface DataSource is
|
||||
method writeData(data)
|
||||
method readData(): data
|
||||
|
||||
class FileDataSource implements DataSource is // ConcreteComponent
|
||||
method writeData(data) is // write to file
|
||||
method readData(): data is // read from file
|
||||
|
||||
class DataSourceDecorator implements DataSource is // base Decorator
|
||||
protected field wrappee: DataSource
|
||||
constructor DataSourceDecorator(source) is wrappee = source
|
||||
method writeData(data) is wrappee.writeData(data)
|
||||
method readData(): data is return wrappee.readData()
|
||||
|
||||
class EncryptionDecorator extends DataSourceDecorator is
|
||||
method writeData(data) is // encrypt, then wrappee.writeData(...)
|
||||
method readData(): data is // wrappee.readData(), then decrypt
|
||||
|
||||
class CompressionDecorator extends DataSourceDecorator is
|
||||
method writeData(data) is // compress, then wrappee.writeData(...)
|
||||
method readData(): data is // wrappee.readData(), then decompress
|
||||
|
||||
// the stack is assembled from configuration, at runtime
|
||||
source = new FileDataSource("salary.dat")
|
||||
if (enabledEncryption) source = new EncryptionDecorator(source)
|
||||
if (enabledCompression) source = new CompressionDecorator(source)
|
||||
```
|
||||
|
||||
Note this example's decorators _are_ order-sensitive: compress-then-encrypt and
|
||||
encrypt-then-compress give different bytes (and wildly different compression
|
||||
ratios).
|
||||
|
||||
## Applicability
|
||||
|
||||
- Add responsibilities to individual objects dynamically and transparently,
|
||||
without affecting other objects.
|
||||
- The responsibilities can be withdrawn again.
|
||||
- Extension by subclassing is impractical: too many independent combinations, or
|
||||
the class is `sealed`/`final` and can't be subclassed at all.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| Extend behavior with no new subclass | Hard to remove one specific wrapper from the middle of a stack |
|
||||
| Add/remove responsibilities at runtime | Hard to keep behavior independent of stack order |
|
||||
| Combine behaviors by stacking | Setup code that assembles the stack gets ugly |
|
||||
| Single Responsibility — one class per behavior | A decorated object is **not** identical to the original — don't rely on reference identity or `GetType()` |
|
||||
| Easy to apply the _same_ decorator twice (for example, a double border) | Yields lots of small look-alike objects; harder to debug |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- `System.IO.Stream` is the canonical case: `GZipStream`, `CryptoStream`, and
|
||||
`BufferedStream` each wrap another `Stream` and are themselves a `Stream` —
|
||||
exactly the GoF pseudocode shown earlier.
|
||||
- ASP.NET Core middleware.
|
||||
- `IMemoryCache`/`HttpMessageHandler` wrappers.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Adapter** changes an interface; Decorator keeps it and adds behavior — and
|
||||
supports recursive composition, which Adapter doesn't.
|
||||
- **Composite** — similar recursive-composition diagram, but a Decorator has
|
||||
exactly one child and _adds responsibilities_, while Composite _sums up_ many
|
||||
children. You can decorate a single node inside a Composite tree.
|
||||
- **Strategy** — Decorator changes an object's _skin_; Strategy changes its
|
||||
_guts_.
|
||||
- **Proxy** — same structure, different intent: a Proxy manages its subject's
|
||||
lifetime and access; a Decorator's composition is always driven by the client.
|
||||
- **Chain of Responsibility** — also recursive, but handlers may _stop_ the
|
||||
request; decorators must never break the flow.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Bridge](bridge.md) · [All patterns](README.md) · **Next →**
|
||||
[Facade](facade.md)
|
||||
177
docs/facade.md
Normal file
177
docs/facade.md
Normal file
@@ -0,0 +1,177 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Facade
|
||||
|
||||
**Category:** Structural | **Source:** `patterns/facade/CarEngineFacade/` (+
|
||||
`CarEngineFacadeRecording/`)
|
||||
|
||||
## Problem
|
||||
|
||||
Using a complex subsystem directly means the client must know every class,
|
||||
instantiate them all, and call their methods **in the correct order**. That
|
||||
sequencing knowledge leaks into — and couples — every piece of business logic
|
||||
that touches the subsystem.
|
||||
|
||||
## Solution
|
||||
|
||||
Add one class that owns the wiring and the call sequence, exposing a couple of
|
||||
high-level methods. The subsystem classes stay completely unaware the facade
|
||||
exists. A facade adds **no new functionality**; it only hides complexity,
|
||||
deliberately trading away some of the subsystem's generality for ease of use.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Facade** → `Facade` — knows which subsystem class handles what, and in what
|
||||
order.
|
||||
- **Subsystem classes** → `AirFlowController`, `FuelInjector`, `Starter`,
|
||||
`CoolingController`, `CatalyticConverter` — do the work, hold no reference to
|
||||
the facade.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Facade {
|
||||
-AirFlowController _air
|
||||
-FuelInjector _fuel
|
||||
-Starter _start
|
||||
-CoolingController _cool
|
||||
-CatalyticConverter _cat
|
||||
+Start()
|
||||
+Stop()
|
||||
}
|
||||
Program --> Facade : only dependency
|
||||
Facade --> AirFlowController
|
||||
Facade --> FuelInjector
|
||||
Facade --> Starter
|
||||
Facade --> CoolingController
|
||||
Facade --> CatalyticConverter
|
||||
```
|
||||
|
||||
Note every arrow points _away_ from the facade. Nothing points back — that
|
||||
one-directionality is what distinguishes Facade from Mediator.
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// The five subsystem classes — real project has empty "// implementation" bodies
|
||||
public class AirFlowController { public void TakeAir() {} public void Off() {} }
|
||||
public class FuelInjector { public void On() {} public void Inject() {} public void Off() {} }
|
||||
public class Starter { public void Start() {} }
|
||||
public class CoolingController { public void Run() {} public void Stop() {} public void Cool() {} }
|
||||
public class CatalyticConverter { public void On() {} public void Off() {} }
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Facade.cs — owns construction AND ordering
|
||||
public class Facade
|
||||
{
|
||||
private readonly AirFlowController _air = new();
|
||||
private readonly CatalyticConverter _cat = new();
|
||||
private readonly CoolingController _cool = new();
|
||||
private readonly FuelInjector _fuel = new();
|
||||
private readonly Starter _start = new();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_air.TakeAir(); // order matters: air before fuel before spark
|
||||
_fuel.On();
|
||||
_fuel.Inject();
|
||||
_start.Start();
|
||||
_cool.Run();
|
||||
_cat.On();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_fuel.Off(); // NOT the reverse of Start() — shutdown has its own order
|
||||
_cat.Off();
|
||||
_cool.Cool(); // keep cooling after fuel is cut, then stop cooling
|
||||
_cool.Stop();
|
||||
_air.Off();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Program.cs — the file keeps the "before" version commented out for comparison
|
||||
|
||||
// WITHOUT the facade — client owns construction AND sequencing:
|
||||
// var airflow = new AirFlowController();
|
||||
// var fuel = new FuelInjector();
|
||||
// var start = new Starter();
|
||||
// var cool = new CoolingController();
|
||||
// var cat = new CatalyticConverter();
|
||||
// airflow.TakeAir(); fuel.On(); fuel.Inject(); start.Start(); cool.Run(); cat.On();
|
||||
// fuel.Off(); cat.Off(); cool.Cool(); cool.Stop(); airflow.Off();
|
||||
|
||||
// WITH the facade — 11 ordered calls across 5 types collapse to 2 calls on 1 type:
|
||||
var facade = new Facade();
|
||||
facade.Start();
|
||||
facade.Stop();
|
||||
```
|
||||
|
||||
That the `Stop()` sequence is _not_ `Start()` reversed is the point of the whole
|
||||
example: the ordering is real domain knowledge, it is easy to get wrong, and it
|
||||
should live in exactly one place.
|
||||
|
||||
## Output (verified)
|
||||
|
||||
**None** — the subsystem method bodies are deliberately empty stubs, so the
|
||||
program runs and exits silently. This example is about the _shape of the
|
||||
dependencies_, not runtime behavior.
|
||||
|
||||
## GoF pseudocode (video conversion library)
|
||||
|
||||
```
|
||||
class VideoConverter is
|
||||
method convert(filename, format): File is
|
||||
file = new VideoFile(filename)
|
||||
sourceCodec = new CodecFactory.extract(file)
|
||||
if (format == "mp4") destinationCodec = new MPEG4CompressionCodec()
|
||||
else destinationCodec = new OggCompressionCodec()
|
||||
buffer = BitrateReader.read(filename, sourceCodec)
|
||||
result = BitrateReader.convert(buffer, destinationCodec)
|
||||
result = (new AudioMixer()).fix(result)
|
||||
return new File(result)
|
||||
|
||||
convertor = new VideoConverter()
|
||||
mp4 = convertor.convert("youtubevideo.ogg", "mp4")
|
||||
```
|
||||
|
||||
## Applicability
|
||||
|
||||
- You want a simple, limited interface to a complex subsystem that satisfies
|
||||
most clients; the few needing more can still reach past the facade.
|
||||
- There are many dependencies between clients and a subsystem's implementation
|
||||
classes.
|
||||
- You want to **layer** subsystems — give each layer a facade and let layers
|
||||
talk only through them.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| Isolates client code from subsystem complexity | Can grow into a god object coupled to everything |
|
||||
| Reduces the number of objects clients deal with; cuts compile-time dependencies | Adds an indirection layer that must be maintained |
|
||||
| Doesn't _prevent_ direct subsystem access when a client needs it | A facade that grows too large needs splitting into refined facades |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- `HttpClient` over the sockets/handler/header stack.
|
||||
- `File.ReadAllText` over `FileStream` + `StreamReader` + encoding detection.
|
||||
- `WebApplication.CreateBuilder` over the hosting/DI/config subsystems.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Adapter** makes an _existing_ interface usable; Facade defines a _new_,
|
||||
simpler one. Adapter usually wraps one object, Facade a whole subsystem.
|
||||
- **Facade vs. Mediator** — both organize collaboration among many classes.
|
||||
Facade is **one-directional** and the subsystem doesn't know it exists;
|
||||
Mediator is **multi-directional** and colleagues explicitly depend on it.
|
||||
- **Abstract Factory** is the alternative when you only want to hide subsystem
|
||||
_object creation_.
|
||||
- A facade is often made a **Singleton**, since one instance usually suffices.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Decorator](decorator.md) · [All patterns](README.md) · **Next →**
|
||||
[Mediator](mediator.md)
|
||||
284
docs/mediator.md
Normal file
284
docs/mediator.md
Normal file
@@ -0,0 +1,284 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Mediator
|
||||
|
||||
**Category:** Behavioral | **Also known as: Intermediary, Controller** |
|
||||
**Source:** `patterns/mediator/AirTrafficControl/`
|
||||
|
||||
## Problem
|
||||
|
||||
An airport tower needs to arbitrate five runways among any number of flights
|
||||
and ground vehicles. Wire that directly, and each `Flight` and `GroundVehicle`
|
||||
needs its own reference to the runway array and to every other aircraft
|
||||
currently occupying a runway. Every colleague then duplicates the same
|
||||
allocation logic. A new colleague type, such as a snowplow, needs the same
|
||||
wiring again.
|
||||
|
||||
## Solution
|
||||
|
||||
Give every colleague exactly one reference: to a **mediator**. Colleagues stop
|
||||
tracking runway state themselves — they send a request (`RequestLanding`,
|
||||
`RequestTakeoff`, `RequestRunwayCrossing`, …) to the mediator and wait to be
|
||||
told `accepted: true/false`. The mediator alone owns the five `Runway` slots
|
||||
and decides who gets a runway. Colleagues never learn about each other at all.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Mediator** → `IMediator` — declares `Notify(sender, request)`.
|
||||
- **ConcreteMediator** → `AirTrafficControlTower` — owns the five runways and
|
||||
is the only class that knows the allocation rules.
|
||||
- **Colleague** → `IColleague` — declares
|
||||
`ReceiveMessage(instruction, accepted)`.
|
||||
- **ConcreteColleague** → `Flight`, `GroundVehicle` — send requests, receive
|
||||
accept/reject callbacks; neither knows the other exists.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class IMediator {
|
||||
<<interface>>
|
||||
+Notify(sender, request)
|
||||
}
|
||||
class IColleague {
|
||||
<<interface>>
|
||||
+ReceiveMessage(instruction, accepted)
|
||||
}
|
||||
class AirTrafficControlTower {
|
||||
-Runway[5] _runways
|
||||
+Notify(sender, request)
|
||||
}
|
||||
class Flight {
|
||||
+RequestLanding()
|
||||
+RequestTakeoff()
|
||||
}
|
||||
class GroundVehicle {
|
||||
+RequestRunwayCrossing()
|
||||
}
|
||||
IMediator <|.. AirTrafficControlTower
|
||||
IColleague <|.. Flight
|
||||
IColleague <|.. GroundVehicle
|
||||
Flight --> IMediator : Notify(this, request)
|
||||
GroundVehicle --> IMediator : Notify(this, request)
|
||||
AirTrafficControlTower --> IColleague : ReceiveMessage(instruction, accepted)
|
||||
```
|
||||
|
||||
Note the arrow direction: `Flight`/`GroundVehicle` only ever point at
|
||||
`IMediator`; `AirTrafficControlTower` only ever points back at `IColleague`.
|
||||
Neither colleague type points at the other.
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// Program.cs — Mediator + Colleague interfaces
|
||||
public interface IColleague
|
||||
{
|
||||
void ReceiveMessage(string instruction, bool accepted);
|
||||
}
|
||||
|
||||
public interface IMediator
|
||||
{
|
||||
void Notify(IColleague sender, AirTrafficRequest request);
|
||||
}
|
||||
|
||||
// Flight — ConcreteColleague: only ever talks to _mediator
|
||||
public class Flight : IColleague
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public string FlightNumber { get; }
|
||||
|
||||
public Flight(string flightNumber, IMediator mediator)
|
||||
{
|
||||
FlightNumber = flightNumber;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public void RequestLanding() =>
|
||||
_mediator.Notify(this, AirTrafficRequest.LandingRequest);
|
||||
|
||||
public void LandingCompleted() =>
|
||||
_mediator.Notify(this, AirTrafficRequest.LandingRequestDone);
|
||||
|
||||
public void ReceiveMessage(string instruction, bool accepted) =>
|
||||
Console.WriteLine(accepted
|
||||
? $"Flight {FlightNumber} received instruction: {instruction}"
|
||||
: $"Flight {FlightNumber} received rejection for instruction: {instruction}");
|
||||
|
||||
// RequestTakeoff() / TakeoffCompleted() follow the same shape
|
||||
}
|
||||
|
||||
// GroundVehicle mirrors Flight exactly, but for RequestRunwayCrossing() /
|
||||
// RunwayCrossingCompleted() — same interface, same "only knows the mediator" shape
|
||||
|
||||
// AirTrafficControlTower — ConcreteMediator: the only class that knows
|
||||
// about runways and occupancy
|
||||
public class AirTrafficControlTower : IMediator
|
||||
{
|
||||
private class Runway
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public IColleague? OccupiedBy { get; set; }
|
||||
public Runway(string name) => Name = name;
|
||||
}
|
||||
|
||||
private readonly Runway[] _runways = new Runway[5];
|
||||
|
||||
public AirTrafficControlTower()
|
||||
{
|
||||
for (int i = 0; i < _runways.Length; i++)
|
||||
_runways[i] = new Runway($"Runway R{i + 1}");
|
||||
}
|
||||
|
||||
public void Notify(IColleague sender, AirTrafficRequest request)
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case AirTrafficRequest.LandingRequest: HandleLandingRequest(sender); break;
|
||||
case AirTrafficRequest.TakeoffRequest: HandleTakeoffRequest(sender); break;
|
||||
case AirTrafficRequest.RunwayCrossingRequest: HandleRunwayCrossingRequest(sender); break;
|
||||
case AirTrafficRequest.LandingRequestDone: HandleLandingRequestDone(sender); break;
|
||||
case AirTrafficRequest.TakeoffRequestDone: HandleTakeoffRequestDone(sender); break;
|
||||
case AirTrafficRequest.RunwayCrossingRequestDone: HandleRunwayCrossingRequestDone(sender); break;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleLandingRequest(IColleague sender)
|
||||
{
|
||||
for (int i = 0; i < _runways.Length; i++)
|
||||
{
|
||||
if (_runways[i].OccupiedBy is null)
|
||||
{
|
||||
_runways[i].OccupiedBy = sender;
|
||||
sender.ReceiveMessage($"Landing approved on {_runways[i].Name}.", accepted: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sender.ReceiveMessage("No available runways for landing.", accepted: false);
|
||||
}
|
||||
|
||||
// HandleTakeoffRequest / HandleRunwayCrossingRequest mirror
|
||||
// HandleLandingRequest; the three Handle*Done methods clear whichever
|
||||
// runway `sender` occupies
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Program.cs — client: flat construction, then requests through the tower only
|
||||
var tower = new AirTrafficControlTower();
|
||||
var flight1 = new Flight("AC101", tower);
|
||||
var vehicle1 = new GroundVehicle("V1", tower);
|
||||
|
||||
flight1.RequestLanding(); // approved: Runway R1 free
|
||||
// ... 4 more requests fill R2–R5 ...
|
||||
vehicle2.RequestRunwayCrossing(); // rejected: no runways left
|
||||
flight1.LandingCompleted(); // frees R1
|
||||
vehicle2.RequestRunwayCrossing(); // now approved, on the freed R1
|
||||
```
|
||||
|
||||
## Output (verified)
|
||||
|
||||
```
|
||||
--- Occupying all 5 runways ---
|
||||
Flight AC101 requesting landing.
|
||||
Flight AC101 received instruction: Landing approved on Runway R1.
|
||||
Flight WJ202 requesting takeoff.
|
||||
Flight WJ202 received instruction: Takeoff approved on Runway R2.
|
||||
Flight DL303 requesting landing.
|
||||
Flight DL303 received instruction: Landing approved on Runway R3.
|
||||
Flight UA404 requesting takeoff.
|
||||
Flight UA404 received instruction: Takeoff approved on Runway R4.
|
||||
Ground vehicle V1 requesting runway crossing.
|
||||
Ground vehicle V1 received instruction: Crossing approved on Runway R5.
|
||||
--- No runways left: this request should be rejected ---
|
||||
Ground vehicle V2 requesting runway crossing.
|
||||
Ground vehicle V2 received rejection for instruction: No available runways for crossing.
|
||||
--- Freeing up a runway: the previously rejected request can now succeed ---
|
||||
Flight AC101 has completed landing.
|
||||
Flight AC101 received instruction: You have cleared Runway R1 after landing.
|
||||
Ground vehicle V2 requesting runway crossing.
|
||||
Ground vehicle V2 received instruction: Crossing approved on Runway R1.
|
||||
--- Clearing the remaining runways ---
|
||||
Flight WJ202 has completed takeoff.
|
||||
Flight WJ202 received instruction: You have cleared Runway R2 after takeoff.
|
||||
Flight DL303 has completed landing.
|
||||
Flight DL303 received instruction: You have cleared Runway R3 after landing.
|
||||
Flight UA404 has completed takeoff.
|
||||
Flight UA404 received instruction: You have cleared Runway R4 after takeoff.
|
||||
Ground vehicle V1 has completed runway crossing.
|
||||
Ground vehicle V1 received instruction: You have cleared Runway R5 after crossing.
|
||||
Ground vehicle V2 has completed runway crossing.
|
||||
Ground vehicle V2 received instruction: You have cleared Runway R1 after crossing.
|
||||
```
|
||||
|
||||
## GoF pseudocode (dialog coordinating its form controls)
|
||||
|
||||
```
|
||||
interface Mediator is
|
||||
method notify(sender: Component, event: string)
|
||||
|
||||
class AuthenticationDialog implements Mediator is
|
||||
field loginOrRegisterChkBx: Checkbox
|
||||
field okBtn: Button
|
||||
method notify(sender, event) is
|
||||
if (sender == loginOrRegisterChkBx and event == "check")
|
||||
// show/hide login vs. registration fields, retitle the dialog
|
||||
if (sender == okBtn and event == "click")
|
||||
// validate credentials or create the account
|
||||
|
||||
class Component is
|
||||
field dialog: Mediator
|
||||
constructor Component(dialog) is this.dialog = dialog
|
||||
method click() is dialog.notify(this, "click")
|
||||
|
||||
class Checkbox extends Component is
|
||||
method check() is dialog.notify(this, "check")
|
||||
```
|
||||
|
||||
This repo's version tracks the canonical shape closely:
|
||||
`Notify(sender, request)` takes the same two arguments as GoF's
|
||||
`notify(sender, event)` — only an `AirTrafficRequest` enum in place of a bare
|
||||
string, and an `IMediator` interface, so colleagues aren't wired to one concrete
|
||||
mediator class. Where the GoF sketch dispatches on `sender` identity inside a
|
||||
single `notify`, this repo dispatches on the request type and lets five
|
||||
`Handle*` methods each scan `_runways` — same idea, more moving parts because
|
||||
there are more request kinds than the two-widget dialog example needs.
|
||||
|
||||
## Applicability
|
||||
|
||||
- A set of objects communicate in well-defined but complex ways, and the
|
||||
resulting interdependencies are unstructured and hard to follow.
|
||||
- Reusing an object is hard because it refers to and talks to many others.
|
||||
- Behavior distributed across several classes should be customizable without
|
||||
lots of subclassing — write a new mediator instead.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Single Responsibility — collaboration logic sits in one place | The mediator can become a God Object / monolith |
|
||||
| Open/Closed — new mediators without touching components | It centralizes control: complexity moves rather than vanishes |
|
||||
| Components decouple and become individually reusable | Five near-identical `Handle*` methods here already show the mediator absorbing complexity the colleagues no longer carry |
|
||||
| Replaces many-to-many interactions with one-to-many | |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- WinForms/WPF code-behind acting as dialog director.
|
||||
- The MediatR library.
|
||||
- SignalR hubs routing between clients.
|
||||
- A real air-traffic-control tower — this repo's own example, and the book's
|
||||
analogy: pilots never talk to each other, only to the tower.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Chain of Responsibility / Command / Mediator / Observer** are the four ways
|
||||
to connect senders and receivers: CoR passes along a chain until handled;
|
||||
Command makes a one-way sender→receiver link; **Mediator removes direct links,
|
||||
forcing a hub**; Observer lets receivers subscribe dynamically.
|
||||
- **Facade vs. Mediator** — directionality (see Facade).
|
||||
- **Mediator vs. Observer** — genuinely blurry. Distinguishing goal: Mediator
|
||||
eliminates mutual dependencies among a known set of components by routing
|
||||
every interaction through a fixed hub; Observer establishes dynamic one-way
|
||||
subscriptions where any number of unknown subscribers can attach later.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Facade](facade.md) · [All patterns](README.md) · **Next →**
|
||||
[Memento](memento.md)
|
||||
275
docs/memento.md
Normal file
275
docs/memento.md
Normal file
@@ -0,0 +1,275 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Memento
|
||||
|
||||
**Category:** Behavioral | **Also known as: Snapshot** | **Source:**
|
||||
`patterns/memento/MementoPattern/`
|
||||
|
||||
## Problem
|
||||
|
||||
You want undo. The obvious approach — some other object reads the target's
|
||||
fields and stores copies — only works if the target exposes its state publicly.
|
||||
That breaks encapsulation, and every later refactor of the target ripples out
|
||||
into whatever was copying its fields.
|
||||
|
||||
## Solution
|
||||
|
||||
Let the state's owner (the **Originator**) produce its own opaque snapshot (the
|
||||
**Memento**) and restore itself from one later. A **Caretaker** holds mementos —
|
||||
in an undo stack — without being able to read or alter them. GoF calls this the
|
||||
**narrow vs. wide interface**: the caretaker sees a narrow interface (it can
|
||||
only hold and pass the memento along); the originator sees a wide one (full
|
||||
access to restore itself).
|
||||
|
||||
In languages with nested classes — "C++, C#, and Java", as _Dive Into Design
|
||||
Patterns_ notes — the classic implementation nests the memento **inside** the
|
||||
originator, which is exactly what this project does.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Originator** → `Originator` (implements `IOriginator`)
|
||||
- **Memento** → `Originator.Memento`, a **private nested class** implementing
|
||||
`IMemento`
|
||||
- **Caretaker** → `Caretaker`, holding a `Stack<IMemento>`
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class IMemento {
|
||||
<<interface>>
|
||||
declaresNothing_theNarrowInterface
|
||||
}
|
||||
class IOriginator {
|
||||
<<interface>>
|
||||
+GetMemento() IMemento
|
||||
+SetMemento(IMemento)
|
||||
}
|
||||
class Originator {
|
||||
-Guid _id
|
||||
+GetMemento() IMemento
|
||||
+SetMemento(IMemento)
|
||||
+UpdateId()
|
||||
}
|
||||
class Memento {
|
||||
<<private nested>>
|
||||
+Guid Id
|
||||
}
|
||||
class Caretaker {
|
||||
-Stack~IMemento~ _mementos
|
||||
-IOriginator _originator
|
||||
+Save()
|
||||
+Undo()
|
||||
}
|
||||
IOriginator <|.. Originator
|
||||
Originator *-- Memento : nested, private
|
||||
IMemento <|.. Memento
|
||||
Caretaker --> IOriginator
|
||||
Caretaker --> IMemento : holds, cannot read
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// IMemento.cs — the narrow interface. It declares NOTHING, on purpose:
|
||||
// that is precisely what stops the Caretaker from reading the snapshot.
|
||||
public interface IMemento
|
||||
{
|
||||
}
|
||||
|
||||
// IOriginator.cs
|
||||
public interface IOriginator
|
||||
{
|
||||
IMemento GetMemento();
|
||||
void SetMemento(IMemento memento);
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Originator.cs — Console.WriteLine calls kept, since the verified output depends on them
|
||||
public class Originator : IOriginator
|
||||
{
|
||||
// Private + nested: only Originator can even name this type, let alone read .Id
|
||||
private class Memento : IMemento
|
||||
{
|
||||
public Guid Id;
|
||||
}
|
||||
|
||||
private Guid _id;
|
||||
|
||||
public Originator()
|
||||
{
|
||||
_id = Guid.NewGuid();
|
||||
Console.WriteLine($"Starting ID: {_id}");
|
||||
}
|
||||
|
||||
public IMemento GetMemento()
|
||||
{
|
||||
Memento memento = new Memento();
|
||||
memento.Id = _id;
|
||||
Console.WriteLine("Creating Memento...");
|
||||
return memento; // handed out as IMemento — opaque to everyone else
|
||||
}
|
||||
|
||||
public void SetMemento(IMemento memento)
|
||||
{
|
||||
if (!(memento is Memento)) // guard: reject foreign IMemento implementations
|
||||
{
|
||||
return;
|
||||
}
|
||||
Console.WriteLine("Restting ID..."); // [sic] typo is in the original
|
||||
_id = (memento as Memento).Id; // downcast gives the WIDE interface
|
||||
Console.WriteLine($"ID is reset to {_id}");
|
||||
}
|
||||
|
||||
public void UpdateId()
|
||||
{
|
||||
Console.WriteLine("Updating Id...");
|
||||
_id = Guid.NewGuid();
|
||||
Console.WriteLine($"Updated ID is now {_id}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Caretaker.cs — holds history, never inspects it
|
||||
public class Caretaker
|
||||
{
|
||||
// container that could hold memento of Originator object
|
||||
private readonly Stack<IMemento> _mementos = new();
|
||||
private readonly IOriginator _originator;
|
||||
|
||||
public Caretaker(IOriginator originator)
|
||||
{
|
||||
_originator = originator;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_mementos.Push(_originator.GetMemento());
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_originator.SetMemento(_mementos.Pop());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Caretaker` cannot reach the `Guid`. It has no member that returns one, and
|
||||
`IMemento` declares nothing — the compiler enforces the encapsulation, not a
|
||||
convention.
|
||||
|
||||
```csharp
|
||||
// Program.cs — the walkthrough (prompts/ReadLine calls elided)
|
||||
Originator originator = new Originator();
|
||||
Caretaker caretaker = new Caretaker(originator);
|
||||
|
||||
caretaker.Save(); // stack: [id0]
|
||||
|
||||
originator.UpdateId(); // → id1 (never saved!)
|
||||
originator.UpdateId(); // → id2
|
||||
caretaker.Save(); // stack: [id0, id2]
|
||||
|
||||
originator.UpdateId(); // → id3
|
||||
caretaker.Undo(); // pop id2 → current = id2 (id3 and id1 are gone)
|
||||
|
||||
originator.UpdateId(); // → id4
|
||||
caretaker.Save(); // stack: [id0, id4]
|
||||
|
||||
originator.UpdateId(); // → id5
|
||||
caretaker.Undo(); // pop id4 → current = id4
|
||||
caretaker.Undo(); // pop id0 → current = id0 (stack now EMPTY)
|
||||
```
|
||||
|
||||
## Output (verified — GUIDs differ every run)
|
||||
|
||||
```
|
||||
Starting ID: cdd24bbf-eac8-40b4-ae9a-9b09db9b2d7c
|
||||
Creating Memento...
|
||||
Updating Id...
|
||||
Updated ID is now e9119540-d546-4c38-bd51-462f90df3e47
|
||||
Updating Id...
|
||||
Updated ID is now fcc5d975-3811-4f13-84ab-2768136a83c7
|
||||
Creating Memento...
|
||||
Updating Id...
|
||||
Updated ID is now 02bafbee-f3e7-46d9-a6be-c757f4378595
|
||||
Restting ID...
|
||||
ID is reset to fcc5d975-3811-4f13-84ab-2768136a83c7 <- undo skipped 02bafbee
|
||||
Updating Id...
|
||||
Updated ID is now ca9f441d-cdf9-4ffa-9269-362291f33235
|
||||
Creating Memento...
|
||||
Updating Id...
|
||||
Updated ID is now b8cfe4d8-de93-4fc9-839d-f63a0a12eb7b
|
||||
Restting ID...
|
||||
ID is reset to ca9f441d-cdf9-4ffa-9269-362291f33235
|
||||
Restting ID...
|
||||
ID is reset to cdd24bbf-eac8-40b4-ae9a-9b09db9b2d7c <- back to the very first ID
|
||||
```
|
||||
|
||||
The two lessons the run makes concrete: **undo returns to the last state that
|
||||
was explicitly `Save()`d, not the previous value** (`e9119540` was never saved,
|
||||
so nothing can ever get back to it); and a stack of mementos gives multi-level
|
||||
undo for free — the last two `Undo()` calls walk all the way back to the
|
||||
starting ID.
|
||||
|
||||
## GoF pseudocode (text editor, with Command as the caretaker)
|
||||
|
||||
```
|
||||
class Editor is // Originator
|
||||
private field text, curX, curY, selectionWidth
|
||||
method createSnapshot(): Snapshot is
|
||||
// Memento is immutable: state passed once, via the constructor
|
||||
return new Snapshot(this, text, curX, curY, selectionWidth)
|
||||
|
||||
class Snapshot is // Memento
|
||||
private field editor: Editor
|
||||
private field text, curX, curY, selectionWidth
|
||||
method restore() is // memento restores its own originator
|
||||
editor.setText(text)
|
||||
editor.setCursor(curX, curY)
|
||||
editor.setSelectionWidth(selectionWidth)
|
||||
|
||||
class Command is // Caretaker
|
||||
private field backup: Snapshot
|
||||
method makeBackup() is backup = editor.createSnapshot()
|
||||
method undo() is if (backup != null) backup.restore()
|
||||
```
|
||||
|
||||
This is the "even stricter encapsulation" variant: the memento links back to its
|
||||
originator and owns `restore()`, so the caretaker never calls a setter on the
|
||||
originator at all. The repo's version instead keeps restoration on the
|
||||
originator (`SetMemento`) — the "intermediate interface" variant.
|
||||
|
||||
## Applicability
|
||||
|
||||
- You must snapshot (part of) an object's state so you can restore it later —
|
||||
undo, but equally transaction rollback on error.
|
||||
- Direct access to that state via getters/setters would expose implementation
|
||||
details and break encapsulation.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| Snapshots without violating encapsulation | Can consume lots of RAM if snapshots are frequent or large |
|
||||
| Simplifies the Originator by moving history-keeping to the Caretaker | The caretaker must track the originator's lifecycle to destroy stale mementos |
|
||||
| Multi-level undo falls out of using a stack | Dynamic languages (PHP/Python/JS) can't truly guarantee the memento stays untouched — C# can |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- `TransactionScope` rollback semantics.
|
||||
- EF Core's change tracker, which holds original values so `Reload()`/rollback
|
||||
can restore them.
|
||||
- The undo stack in any editor control.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Command + Memento** is the classic undo pairing: the command captures a
|
||||
memento immediately before mutating the target, and restores it on undo.
|
||||
- **Iterator** — a memento can capture iteration state, letting it roll back.
|
||||
- **Prototype** is a simpler substitute when the object is straightforward and
|
||||
holds no external resource links.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Mediator](mediator.md) · [All patterns](README.md) · **Next →**
|
||||
[Observer](observer.md)
|
||||
271
docs/observer.md
Normal file
271
docs/observer.md
Normal file
@@ -0,0 +1,271 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Observer
|
||||
|
||||
**Category:** Behavioral | **Also known as: Event-Subscriber, Listener** |
|
||||
**Source:** `patterns/observer/ObserverPattern/` (+ `UpdatedOne/`)
|
||||
|
||||
## Problem
|
||||
|
||||
One object's state change must reach an open-ended, changing set of others.
|
||||
Polling (the customer visiting the store daily) wastes the observers' effort;
|
||||
broadcasting to everyone regardless of interest (the store emailing all
|
||||
customers) wastes the publisher's and annoys the uninterested.
|
||||
|
||||
## Solution
|
||||
|
||||
Give the state-holder (**Subject**/Publisher) a subscription list plus
|
||||
subscribe/unsubscribe methods. On a state change it walks the list, calling one
|
||||
common `Update()` method on each subscriber. All subscribers implement a single
|
||||
interface, so the publisher never knows their concrete types — new subscriber
|
||||
types can appear with **zero** changes to the publisher.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Subject** → `Subject` — the reusable subscription machinery.
|
||||
- **Observer** → `IObserver` — declares `Update()`.
|
||||
- **ConcreteSubject** → `Data` — holds the interesting state, calls `Notify()`
|
||||
on change.
|
||||
- **ConcreteObserver** → `BarGraph`, `PieChart` — each reacts, and pulls the new
|
||||
value back from the subject.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class IObserver {
|
||||
<<interface>>
|
||||
+Update()
|
||||
}
|
||||
class Subject {
|
||||
-List~IObserver~ Observers
|
||||
+Subscribe(IObserver)
|
||||
+Unsubscribe(IObserver)
|
||||
+Notify()
|
||||
}
|
||||
class Data {
|
||||
-int Value
|
||||
+SetData(int)
|
||||
+GetData() int
|
||||
}
|
||||
class BarGraph {
|
||||
-Data _data
|
||||
+Update()
|
||||
}
|
||||
class PieChart {
|
||||
-Data _data
|
||||
+Update()
|
||||
}
|
||||
Subject <|-- Data
|
||||
IObserver <|.. BarGraph
|
||||
IObserver <|.. PieChart
|
||||
Subject o--> IObserver : notifies - abstract coupling
|
||||
BarGraph --> Data : pulls value on update
|
||||
PieChart --> Data : pulls value on update
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// IObserver.cs — Observer interface
|
||||
public interface IObserver
|
||||
{
|
||||
void Update(); // called when the Subject updates
|
||||
}
|
||||
|
||||
// Subject.cs — reusable subscription machinery, knows nothing about BarGraph or PieChart
|
||||
public class Subject
|
||||
{
|
||||
private readonly List<IObserver> _observers = new();
|
||||
|
||||
public void Subscribe(IObserver observer) { _observers?.Add(observer); }
|
||||
public void Unsubscribe(IObserver observer) { _observers?.Remove(observer); }
|
||||
|
||||
public void Notify()
|
||||
{
|
||||
foreach (IObserver observer in _observers)
|
||||
observer.Update(); // abstract coupling: only ever the interface
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Data.cs — ConcreteSubject; inherits the subscription machinery
|
||||
public class Data : Subject
|
||||
{
|
||||
private int _value;
|
||||
|
||||
public Data(int value) { _value = value; }
|
||||
|
||||
public void SetData(int value)
|
||||
{
|
||||
_value = value;
|
||||
Notify(); // every mutation fans out
|
||||
}
|
||||
|
||||
public int GetData() { return _value; }
|
||||
}
|
||||
|
||||
// BarGraph.cs — ConcreteObserver
|
||||
public class BarGraph : IObserver
|
||||
{
|
||||
private readonly Data _data;
|
||||
|
||||
public BarGraph(Data data) { _data = data; }
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Console.WriteLine("Bar Graph:");
|
||||
Console.WriteLine("\tGot an update notification");
|
||||
Console.WriteLine("\tUpdating my graph with value " + _data.GetData());
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
// PieChart.cs — ConcreteObserver; same shape as BarGraph, its own message text
|
||||
public class PieChart : IObserver
|
||||
{
|
||||
private readonly Data _data;
|
||||
|
||||
public PieChart(Data data) { _data = data; }
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Console.WriteLine("Pie Chart:");
|
||||
Console.WriteLine("\tGot an update notification");
|
||||
Console.WriteLine("\tUpdating my chart with value " + _data.GetData());
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the **pull** style: `Update()` takes no parameters, so the observer
|
||||
calls `_data.GetData()` to fetch what changed. The alternative **push** style
|
||||
would be `Update(int newValue)`. Pull keeps the interface stable as the
|
||||
subject's state grows; push avoids the observer needing a back-reference to the
|
||||
subject at all. GoF: _"The publisher can pass itself as an argument, letting
|
||||
subscriber fetch any required data directly."_
|
||||
|
||||
Subscription itself is explicit, not automatic — neither `BarGraph` nor
|
||||
`PieChart` calls `Subscribe` on itself. `Program.cs` constructs each observer
|
||||
and then wires it up separately, so the client controls exactly when an observer
|
||||
starts listening:
|
||||
|
||||
```csharp
|
||||
// Program.cs — two independent subscribers on one subject
|
||||
Data data = new Data(2);
|
||||
|
||||
BarGraph bar = new BarGraph(data);
|
||||
PieChart pie = new PieChart(data);
|
||||
|
||||
data.Subscribe(bar);
|
||||
data.Subscribe(pie);
|
||||
|
||||
data.SetData(3);
|
||||
data.SetData(60);
|
||||
```
|
||||
|
||||
Note `Data` never mentions `BarGraph` or `PieChart` by name. A third subscriber
|
||||
type could be added tomorrow without touching `Data` or `Subject` — that is the
|
||||
Open/Closed payoff.
|
||||
|
||||
## Output (verified)
|
||||
|
||||
```
|
||||
Bar Graph:
|
||||
Got an update notification
|
||||
Updating my graph with value 3
|
||||
|
||||
Pie Chart:
|
||||
Got an update notification
|
||||
Updating my chart with value 3
|
||||
|
||||
Bar Graph:
|
||||
Got an update notification
|
||||
Updating my graph with value 60
|
||||
|
||||
Pie Chart:
|
||||
Got an update notification
|
||||
Updating my chart with value 60
|
||||
```
|
||||
|
||||
Both subscribers fire on each `SetData`, and each _pulls_ the current value.
|
||||
They happen to run in subscription order here because the backing store is a
|
||||
`List<T>` — but GoF and Shvets both warn that **notification order is not part
|
||||
of the contract**; never write an observer that depends on running before or
|
||||
after another.
|
||||
|
||||
## GoF pseudocode (typed events via a dedicated event manager)
|
||||
|
||||
```
|
||||
class EventManager is
|
||||
private field listeners: hash map of event types and listeners
|
||||
method subscribe(eventType, listener) is listeners.add(eventType, listener)
|
||||
method unsubscribe(eventType, listener) is listeners.remove(eventType, listener)
|
||||
method notify(eventType, data) is
|
||||
foreach (listener in listeners.of(eventType)) do listener.update(data)
|
||||
|
||||
class Editor is
|
||||
private field events: EventManager // composition, not inheritance
|
||||
constructor Editor() is events = new EventManager()
|
||||
method openFile(path) is
|
||||
this.file = new File(path)
|
||||
events.notify("open", file.name)
|
||||
method saveFile() is
|
||||
file.write()
|
||||
events.notify("save", file.name)
|
||||
|
||||
interface EventListener is
|
||||
method update(filename)
|
||||
|
||||
class LoggingListener implements EventListener is
|
||||
method update(filename) is log.write(...)
|
||||
class EmailAlertsListener implements EventListener is
|
||||
method update(filename) is system.email(...)
|
||||
|
||||
editor.events.subscribe("open", logger)
|
||||
editor.events.subscribe("save", emailAlerts)
|
||||
```
|
||||
|
||||
Two upgrades over the repo version worth knowing: **event types**, so
|
||||
subscribers pick which events they want; and **composition over inheritance** —
|
||||
`Editor` _has an_ `EventManager` rather than _being a_ `Subject`. The repo uses
|
||||
inheritance (`Data : Subject`), which burns `Data`'s single base class. Shvets
|
||||
flags exactly this: put subscription logic in a separate object when the
|
||||
publisher already has a superclass.
|
||||
|
||||
## Applicability
|
||||
|
||||
- An abstraction has two aspects, one dependent on the other — separate them to
|
||||
vary and reuse them independently.
|
||||
- A change to one object requires changing others, and you don't know how many.
|
||||
- An object should notify others **without assumptions about who they are**.
|
||||
- Objects should observe others only for a limited time or in specific cases —
|
||||
the subscription list is dynamic.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| Open/Closed — new subscribers without touching the publisher | Subscribers are notified in unspecified order |
|
||||
| Relationships can be made and broken at runtime | **Unexpected updates**: one innocuous change can cascade into a storm of updates |
|
||||
| Abstract, minimal coupling — subject and observers can live in different layers | Forgetting to unsubscribe leaks the observer (see Pitfalls) |
|
||||
| Supports broadcast: the sender needn't name a receiver | |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- C# `event`/`delegate` — Observer built into the language.
|
||||
- `IObservable<T>`/`IObserver<T>` (Rx) — the GoF interface pair almost verbatim,
|
||||
plus the `IDisposable` subscription handle that fixes the unsubscribe problem.
|
||||
- `INotifyPropertyChanged` powering WPF data binding.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- One of the four sender/receiver connection strategies alongside **Chain of
|
||||
Responsibility, Command, Mediator** — Observer's distinguishing trait is
|
||||
_dynamic_ subscribe/unsubscribe.
|
||||
- **Mediator vs. Observer** — see the discussion under Mediator; the two are
|
||||
often combined, with the mediator acting as publisher.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Memento](memento.md) · [All patterns](README.md) · **Next →**
|
||||
[State](state.md)
|
||||
37
docs/principles.md
Normal file
37
docs/principles.md
Normal file
@@ -0,0 +1,37 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Principles these examples keep demonstrating
|
||||
|
||||
These seven patterns all apply the same handful of ideas from _Dive Into Design
|
||||
Patterns_' design-principles chapter:
|
||||
|
||||
- **Encapsulate what varies.** Each pattern isolates the one thing that changes:
|
||||
the platform (Bridge), the added behavior (Decorator), the subsystem wiring
|
||||
(Facade), the collaboration rules (Mediator), the saved state (Memento), the
|
||||
reaction to a change (Observer), or the state-specific behavior (State).
|
||||
Everything else stays untouched.
|
||||
|
||||
- **Program to an interface, not an implementation.** `IMessageSender`,
|
||||
`IShape`, `IObserver`, `IMemento`, `IOriginator`, and `State` all exist so
|
||||
that a context or client depends on a contract instead of a concrete type.
|
||||
`Subject.Notify()` calls `observer.Update()` through `IObserver` alone, which
|
||||
is exactly why `Data` never needs to know that `PieChart` or `BarGraph`
|
||||
exists.
|
||||
|
||||
- **Favor composition over inheritance.** Bridge, Decorator, Mediator, and State
|
||||
all answer "my class hierarchy is exploding" the same way: give the object a
|
||||
_reference_ to another object instead of subclassing again. Shvets: _"with
|
||||
composition you can easily substitute the linked 'helper' object with another,
|
||||
changing the behavior of the container at runtime."_
|
||||
|
||||
- **Single Responsibility.** _"A class should have just one reason to change."_
|
||||
Visible in `ShapeDecorator` subclasses (one behavior each), the `State`
|
||||
subclasses (one state each), and `Facade` (all engine sequencing in one
|
||||
place).
|
||||
|
||||
- **Open/Closed.** _"Classes should be open for extension but closed for
|
||||
modification."_ The concrete test: adding a new `IObserver`, a new
|
||||
`ShapeDecorator`, a new `IMessageSender`, or a new `State` requires **zero**
|
||||
edits to existing classes. Adding a state to `BadCarState` requires editing
|
||||
four separate `switch` statements — that's the principle failing, made
|
||||
visible.
|
||||
359
docs/state.md
Normal file
359
docs/state.md
Normal file
@@ -0,0 +1,359 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# State
|
||||
|
||||
**Category:** Behavioral | **Source:** `patterns/state/BadCarState/` +
|
||||
`patterns/state/CarState/`
|
||||
|
||||
## Problem
|
||||
|
||||
Behavior depends on an internal state field, so every state-dependent method
|
||||
`switch`es on it. As states and behaviors multiply, each method grows a branch
|
||||
per state, and changing a transition rule means finding and editing that
|
||||
conditional in _every_ method that has one.
|
||||
|
||||
## Solution
|
||||
|
||||
Make one class per state, all sharing a common `State` base class. The
|
||||
**context** holds a reference to its _current_ state object and delegates
|
||||
state-dependent calls to it instead of branching. Each state class implements
|
||||
only what makes sense in that state, and states drive transitions by handing the
|
||||
context a new state object. The object "appears to change its class" because
|
||||
you're swapping what it delegates to.
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Context** → `Car` — holds the current `State`, delegates, exposes
|
||||
`ChangeState`.
|
||||
- **State** → `State` base class — declares the state-specific operations.
|
||||
- **ConcreteState** → `Off`, `Stopped`, `Driving`.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Off
|
||||
Off --> Stopped : turn-clockwise / engine has started
|
||||
Stopped --> Off : turn-counter / engine has stopped
|
||||
Stopped --> Driving : gas / car begins to drive
|
||||
Driving --> Stopped : brake / car slows to a stop
|
||||
Driving --> Off : turn-counter / engine off, car stops suddenly
|
||||
Stopped --> Stopped : turn-clockwise / grinding noise
|
||||
Driving --> Driving : turn-clockwise / grinding noise
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Car {
|
||||
-State _state
|
||||
+HandleInput(string) bool
|
||||
+ChangeState(State)
|
||||
}
|
||||
class State {
|
||||
#Car _car
|
||||
+TurnKeyClockwise() virtual
|
||||
+TurnKeyCounterClockwise() virtual
|
||||
+PressGas() virtual
|
||||
+PressBrake() virtual
|
||||
}
|
||||
Car o--> State : delegates
|
||||
State <|-- Off
|
||||
State <|-- Stopped
|
||||
State <|-- Driving
|
||||
Off ..> Car : calls ChangeState
|
||||
Stopped ..> Car : calls ChangeState
|
||||
Driving ..> Car : calls ChangeState
|
||||
```
|
||||
|
||||
## Before — `BadCarState`: a `switch` inside a `switch`
|
||||
|
||||
```csharp
|
||||
public class Car
|
||||
{
|
||||
private enum State { Off, Stopped, Driving }
|
||||
private State _state = State.Off;
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "turn-clockwise":
|
||||
switch (_state) // <-- nested state switch #1
|
||||
{
|
||||
case State.Off:
|
||||
Console.WriteLine("The engine has started");
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
case State.Stopped:
|
||||
case State.Driving:
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "turn-counter":
|
||||
switch (_state) // <-- nested state switch #2
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The engine has stopped");
|
||||
_state = State.Off;
|
||||
break;
|
||||
case State.Driving:
|
||||
Console.WriteLine("The engine turns off, and the car stops suddenly");
|
||||
_state = State.Off;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "gas":
|
||||
switch (_state) // <-- nested state switch #3
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The car begins to drive");
|
||||
_state = State.Driving;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "brake":
|
||||
switch (_state) // <-- nested state switch #4
|
||||
{
|
||||
case State.Driving:
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "exit":
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adding a `Reversing` state means auditing **all four** nested switches. And
|
||||
notice there is no `case State.Off` under `"gas"` — pressing the gas while off
|
||||
silently does nothing, and nothing in the code says whether that was intentional
|
||||
or an oversight.
|
||||
|
||||
## After — `CarState`: one class per state
|
||||
|
||||
```csharp
|
||||
// States/State.cs — base State. Virtual empty bodies = "this input is a no-op here."
|
||||
public class State
|
||||
{
|
||||
protected Car _car; // backreference, so states can transition
|
||||
|
||||
public State(Car car) { _car = car; }
|
||||
|
||||
public virtual void TurnKeyClockwise() { }
|
||||
public virtual void TurnKeyCounterClockwise() { }
|
||||
public virtual void PressGas() { }
|
||||
public virtual void PressBrake() { }
|
||||
}
|
||||
|
||||
// States/Off.cs — overrides exactly one operation; the rest are inherited no-ops
|
||||
public class Off : State
|
||||
{
|
||||
public Off(Car car) : base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has started");
|
||||
_car.ChangeState(new Stopped(_car)); // the state drives its own transition
|
||||
}
|
||||
}
|
||||
|
||||
// States/Stopped.cs
|
||||
public class Stopped : State
|
||||
{
|
||||
public Stopped(Car car) : base(car) { }
|
||||
|
||||
public override void PressGas()
|
||||
{
|
||||
Console.WriteLine("The car begins to drive");
|
||||
_car.ChangeState(new Driving(_car));
|
||||
}
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise..."); // valid input, no transition
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has stopped");
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
}
|
||||
|
||||
// States/Driving.cs
|
||||
public class Driving : State
|
||||
{
|
||||
public Driving(Car car) : base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine turns off, and the car stops suddenly");
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
|
||||
public override void PressBrake()
|
||||
{
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
_car.ChangeState(new Stopped(_car));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Car.cs — the Context. All state branching is gone.
|
||||
public class Car
|
||||
{
|
||||
private State _state;
|
||||
|
||||
public Car()
|
||||
{
|
||||
ChangeState(new Off(this)); // initial state
|
||||
}
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input) // switches on INPUT only, never on state
|
||||
{
|
||||
case "turn-clockwise": _state.TurnKeyClockwise(); break;
|
||||
case "turn-counter": _state.TurnKeyCounterClockwise(); break;
|
||||
case "gas": _state.PressGas(); break;
|
||||
case "brake": _state.PressBrake(); break;
|
||||
case "exit": return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ChangeState(State state) { _state = state; }
|
||||
}
|
||||
```
|
||||
|
||||
What actually improved:
|
||||
|
||||
- `Car` went from four nested state-switches to four one-line delegations. It no
|
||||
longer knows the state names at all beyond the initial `Off`.
|
||||
- Adding `Reversing` = adding **one file**. No existing method changes.
|
||||
- Each state's complete behavior is readable in one place, instead of scattered
|
||||
across four switch arms.
|
||||
- "Gas while off does nothing" is now an explicit, deliberate design statement:
|
||||
`Off` doesn't override `PressGas`, so the base class's empty body runs.
|
||||
- **Trade-off:** the transition graph is now distributed across the state
|
||||
classes rather than visible in one switch. That's why the state diagram shown
|
||||
earlier is worth keeping next to the code.
|
||||
|
||||
## Output (verified — both projects, same scripted input)
|
||||
|
||||
Input: `gas`, `turn-clockwise`, `gas`, `turn-clockwise`, `brake`,
|
||||
`turn-counter`, `exit`
|
||||
|
||||
```
|
||||
Welcome to the Driving Simulater
|
||||
|
||||
Usage:
|
||||
turn-clockwise -Turns the car's key clockwise
|
||||
turn-counter -Turns the car's key counter clockwise
|
||||
gas -Presses the gas pedal
|
||||
brake -Presses the brake pedal
|
||||
exit -Exits the simulator
|
||||
|
||||
> <- "gas" while Off: silently ignored
|
||||
>
|
||||
The engine has started
|
||||
>
|
||||
The car begins to drive
|
||||
>
|
||||
You hear a grinding noise...
|
||||
>
|
||||
The car slows to a stop
|
||||
>
|
||||
The engine has stopped
|
||||
>
|
||||
|
||||
goodbye
|
||||
```
|
||||
|
||||
`BadCarState` and `CarState` produce **byte-identical output**. That's the
|
||||
headline: the refactor changed structure and nothing else. The first blank
|
||||
prompt is the silent no-op discussed earlier, present in both versions.
|
||||
|
||||
## GoF pseudocode (audio player)
|
||||
|
||||
```
|
||||
class AudioPlayer is // Context
|
||||
field state: State
|
||||
constructor AudioPlayer() is this.state = new ReadyState(this)
|
||||
method changeState(state: State) is this.state = state
|
||||
method clickPlay() is state.clickPlay() // pure delegation
|
||||
method startPlayback() is // service method a state can call back into
|
||||
|
||||
abstract class State is
|
||||
protected field player: AudioPlayer
|
||||
constructor State(player) is this.player = player
|
||||
abstract method clickLock()
|
||||
abstract method clickPlay()
|
||||
|
||||
class ReadyState extends State is
|
||||
method clickPlay() is
|
||||
player.startPlayback()
|
||||
player.changeState(new PlayingState(player))
|
||||
|
||||
class PlayingState extends State is
|
||||
method clickPlay() is
|
||||
player.stopPlayback()
|
||||
player.changeState(new ReadyState(player))
|
||||
|
||||
class LockedState extends State is
|
||||
method clickPlay() is // Locked, so do nothing.
|
||||
```
|
||||
|
||||
GoF makes `State` **abstract** with abstract methods, forcing every state to
|
||||
answer every input explicitly. The repo instead uses virtual empty bodies, so
|
||||
"do nothing" is the default. Abstract = the compiler catches states you forgot
|
||||
to handle; virtual = less boilerplate. Pick deliberately.
|
||||
|
||||
## Applicability
|
||||
|
||||
- An object's behavior depends on its state and must change at run-time
|
||||
accordingly, the number of states is large, and state-specific code changes
|
||||
often.
|
||||
- Operations have large multipart conditionals branching on the object's state
|
||||
(usually enum values), and _several_ operations repeat that same conditional
|
||||
structure.
|
||||
- Lots of duplicate code across similar states — factor it into intermediate
|
||||
abstract state classes.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| Single Responsibility — each state's code in its own class | Overkill for a machine with few states that rarely change |
|
||||
| Open/Closed — add states without touching existing ones | More classes; the transition graph is no longer visible in one place |
|
||||
| Kills the bulky conditionals in the context | Allocates a new state object on every transition (usually irrelevant; states can be cached/shared if stateless) |
|
||||
| Makes transitions explicit instead of stray field assignments | |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- `TcpConnection`-style protocol handling — GoF's own example.
|
||||
- Workflow/order-status engines.
|
||||
- Game character controllers.
|
||||
- `HttpClient`'s connection lifecycle.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Bridge / State / Strategy** share the composition-delegation structure and
|
||||
differ in intent.
|
||||
- **State vs. Strategy** — State is arguably Strategy where the delegates _know
|
||||
about each other_ and actively swap themselves in
|
||||
(`_car.ChangeState(new Stopped(_car))`). Strategy objects are independent and
|
||||
mutually unaware, and are chosen by the client rather than by each other.
|
||||
|
||||
---
|
||||
|
||||
**← Prev:** [Observer](observer.md) · [All patterns](README.md)
|
||||
13
dotnet-tools.json
Normal file
13
dotnet-tools.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "1.3.0",
|
||||
"commands": [
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
13
patterns/bridge/Bridge Example/AlertNotification.cs
Normal file
13
patterns/bridge/Bridge Example/AlertNotification.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This is one of the refined abstraction classes. Note that it can make use of _sender since it inherits it from its superclass. Note also that since EmailSender and SmsSender implement IMessageSender, their specific SendMessage() method will be called, depending on what's passed into the constructor (see the main method)
|
||||
public class AlertNotification : Notification
|
||||
{
|
||||
public AlertNotification(IMessageSender sender)
|
||||
: base(sender) { }
|
||||
|
||||
public override void Notify(string message)
|
||||
{
|
||||
_sender.SendMessage($"ALERT: {message}");
|
||||
}
|
||||
}
|
||||
7
patterns/bridge/Bridge Example/Bridge Example.csproj
Normal file
7
patterns/bridge/Bridge Example/Bridge Example.csproj
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>Bridge_Example</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
16
patterns/bridge/Bridge Example/EmailSender.cs
Normal file
16
patterns/bridge/Bridge Example/EmailSender.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This is one of the concrete implementors, which implements the implementor interface IMessageSender
|
||||
public class EmailSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"Email sent: {message}");
|
||||
}
|
||||
}
|
||||
7
patterns/bridge/Bridge Example/IMessageSender.cs
Normal file
7
patterns/bridge/Bridge Example/IMessageSender.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This class is the implementor. It gets implemented by concrete classes.
|
||||
public interface IMessageSender
|
||||
{
|
||||
void SendMessage(string message);
|
||||
}
|
||||
14
patterns/bridge/Bridge Example/Notification.cs
Normal file
14
patterns/bridge/Bridge Example/Notification.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This is the abstraction
|
||||
public abstract class Notification
|
||||
{
|
||||
protected readonly IMessageSender _sender;
|
||||
|
||||
protected Notification(IMessageSender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
public abstract void Notify(string message);
|
||||
}
|
||||
17
patterns/bridge/Bridge Example/Program.cs
Normal file
17
patterns/bridge/Bridge Example/Program.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Bridge_Example;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
Notification n1 = new AlertNotification(new EmailSender());
|
||||
Notification n2 = new AlertNotification(new SmsSender());
|
||||
Notification n3 = new ReminderNotification(new EmailSender());
|
||||
Notification n4 = new ReminderNotification(new SmsSender());
|
||||
|
||||
n1.Notify("Server is down");
|
||||
n2.Notify("Server is down");
|
||||
n3.Notify("Meeting at 3pm");
|
||||
n4.Notify("Meeting at 3pm");
|
||||
}
|
||||
}
|
||||
13
patterns/bridge/Bridge Example/ReminderNotification.cs
Normal file
13
patterns/bridge/Bridge Example/ReminderNotification.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This is one of the refined abstraction classes. Note that it can make use of _sender since it inherits it from its superclass. Note also that since EmailSender and SmsSender implement IMessageSender, their specific SendMessage() method will be called, depending on what's passed into the constructor (see the main method)
|
||||
public class ReminderNotification : Notification
|
||||
{
|
||||
public ReminderNotification(IMessageSender sender)
|
||||
: base(sender) { }
|
||||
|
||||
public override void Notify(string message)
|
||||
{
|
||||
_sender.SendMessage($"Reminder: {message}");
|
||||
}
|
||||
}
|
||||
16
patterns/bridge/Bridge Example/SmsSender.cs
Normal file
16
patterns/bridge/Bridge Example/SmsSender.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Bridge_Example;
|
||||
|
||||
//This is another concrete implementor class, which implements the implementor interface IMessageSender
|
||||
public class SmsSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"SMS sent: {message}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
102
patterns/decorator/IceCreamDecorations/Program.cs
Normal file
102
patterns/decorator/IceCreamDecorations/Program.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
public interface IIceCream
|
||||
{
|
||||
void Prepare();
|
||||
decimal GetPrice();
|
||||
}
|
||||
|
||||
public class ChocolateIceCream : IIceCream
|
||||
{
|
||||
public void Prepare()
|
||||
{
|
||||
Console.WriteLine("Preparing a chocolate ice cream.");
|
||||
}
|
||||
|
||||
public decimal GetPrice()
|
||||
{
|
||||
return 2.50m; // base price for chocolate ice cream
|
||||
}
|
||||
}
|
||||
|
||||
public class VanillaIceCream : IIceCream
|
||||
{
|
||||
public void Prepare()
|
||||
{
|
||||
Console.WriteLine("Preparing a vanilla ice cream.");
|
||||
}
|
||||
|
||||
public decimal GetPrice()
|
||||
{
|
||||
return 2.00m; // base price for vanilla ice cream
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class IceCreamDecorator(IIceCream decorated) : IIceCream
|
||||
{
|
||||
protected IIceCream _decorated = decorated;
|
||||
public abstract void Prepare();
|
||||
public abstract decimal GetPrice();
|
||||
}
|
||||
|
||||
public class SprinkleDecorator(IIceCream decorated)
|
||||
: IceCreamDecorator(decorated)
|
||||
{
|
||||
public override void Prepare()
|
||||
{
|
||||
// call the base first
|
||||
_decorated.Prepare();
|
||||
Console.WriteLine("Adding sprinkles!");
|
||||
}
|
||||
|
||||
public override decimal GetPrice()
|
||||
{
|
||||
return _decorated.GetPrice() + 0.50m; // add price for sprinkles
|
||||
}
|
||||
}
|
||||
|
||||
public class ChocolateSyrupDecorator(IIceCream decorated)
|
||||
: IceCreamDecorator(decorated)
|
||||
{
|
||||
public override void Prepare()
|
||||
{
|
||||
// call the base prepare first
|
||||
_decorated.Prepare();
|
||||
Console.WriteLine("Adding chocolate syrup!");
|
||||
}
|
||||
|
||||
public override decimal GetPrice()
|
||||
{
|
||||
return _decorated.GetPrice() + 0.75m; // add price for chocolate syrup
|
||||
}
|
||||
}
|
||||
|
||||
public class CaramelSyrupDecorator(IIceCream decorated)
|
||||
: IceCreamDecorator(decorated)
|
||||
{
|
||||
public override void Prepare()
|
||||
{
|
||||
// call the base prepare first
|
||||
_decorated.Prepare();
|
||||
Console.WriteLine("Adding caramel syrup!");
|
||||
}
|
||||
|
||||
public override decimal GetPrice()
|
||||
{
|
||||
return _decorated.GetPrice() + 0.75m; // add price for caramel syrup
|
||||
}
|
||||
}
|
||||
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
decimal price = new CaramelSyrupDecorator(
|
||||
new ChocolateSyrupDecorator(
|
||||
new SprinkleDecorator(
|
||||
new ChocolateIceCream()
|
||||
)
|
||||
)
|
||||
).GetPrice();
|
||||
|
||||
Console.WriteLine($"Total price: {price:C}");
|
||||
}
|
||||
}
|
||||
66
patterns/decorator/ShapeDecorations/Program.cs
Normal file
66
patterns/decorator/ShapeDecorations/Program.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using ShapeDecorations.Shapes;
|
||||
using ShapeDecorations.Shapes.Decorators;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
// scoped to allow for re-use of variable names
|
||||
{
|
||||
// First create a concrete shape, since we need one to be able to decorate anything
|
||||
var circle = new Circle();
|
||||
circle.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Now we decorate the shape with a red color
|
||||
var red = new RedColor(circle);
|
||||
red.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Since the decorator itself is an IShape, we can further decorate it
|
||||
var blueBorder = new BlueBorder(red);
|
||||
blueBorder.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// And can continue to decorate each ShapeDecorator with as many decorator classes as we like
|
||||
var huge = new HugeSize(blueBorder);
|
||||
huge.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
{
|
||||
// Always must start with a concrete IShape implementation
|
||||
var rect = new Rectangle();
|
||||
rect.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Note that the order in which we decorate doesn't matter
|
||||
var blueBorder = new BlueBorder(rect);
|
||||
blueBorder.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
var huge = new HugeSize(blueBorder);
|
||||
huge.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
var red = new RedColor(huge);
|
||||
red.Draw();
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>ShapeDecorations</RootNamespace>
|
||||
<AssemblyName>ShapeDecorations</AssemblyName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
15
patterns/decorator/ShapeDecorations/Shapes/Circle.cs
Normal file
15
patterns/decorator/ShapeDecorations/Shapes/Circle.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations.Shapes;
|
||||
|
||||
public class Circle : IShape
|
||||
{
|
||||
public void Draw()
|
||||
{
|
||||
Console.WriteLine("Drawing a circle");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations.Shapes.Decorators;
|
||||
|
||||
/// <summary>
|
||||
/// See the RedColor class for details on the implementation
|
||||
/// </summary>
|
||||
public class BlueBorder : ShapeDecorator
|
||||
{
|
||||
/// <summary>
|
||||
/// See the RedColor class for details on the implementation
|
||||
/// </summary>
|
||||
public BlueBorder(IShape decorated)
|
||||
: base(decorated) { }
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
_decorated.Draw();
|
||||
Console.WriteLine("\tWith a blue border");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations.Shapes.Decorators;
|
||||
|
||||
public class HugeSize : ShapeDecorator
|
||||
{
|
||||
/// <summary>
|
||||
/// See the RedColor class for details on the implementation
|
||||
/// </summary>
|
||||
/// <param name="decorated">
|
||||
/// The IShape (whtether concrete IShape or concrete
|
||||
/// ShapeDecorator) to be decorated
|
||||
/// </param>
|
||||
public HugeSize(IShape decorated)
|
||||
: base(decorated) { }
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
_decorated.Draw();
|
||||
Console.WriteLine("\tThat is enourmous in size");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations.Shapes.Decorators;
|
||||
|
||||
public class RedColor : ShapeDecorator
|
||||
{
|
||||
/// <summary>
|
||||
/// Public ctor. Calls the base class with the passed IShape, thus decorating
|
||||
/// it.
|
||||
/// Implementation is left empty, as the base class does all the work needed.
|
||||
/// </summary>
|
||||
/// <param name="decorated"></param>
|
||||
public RedColor(IShape decorated)
|
||||
: base(decorated) { }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual drawing logic, but first calls the decorated member,
|
||||
/// adding its own logic afterwards.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
// First calls decorated member
|
||||
_decorated.Draw();
|
||||
|
||||
// Then adds additional drawing logic
|
||||
Console.WriteLine("\tWhich is red in color");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace ShapeDecorations.Shapes.Decorators;
|
||||
|
||||
public abstract class ShapeDecorator : IShape
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a reference to the shape which it decorates
|
||||
/// this allows implementors to add additional steps to the "drawing" process.
|
||||
/// Marked "protected" so that inheriting classes have access to it.
|
||||
/// </summary
|
||||
protected IShape _decorated;
|
||||
|
||||
/// <summary>
|
||||
/// Acceps a reference to any IShape.
|
||||
/// Since the ShapeDecorator itself IS an IShape, this
|
||||
/// decorator can also decorate other decorators
|
||||
/// However, since a ShapeDecorator has to be instantiated with an existing
|
||||
/// IShape,
|
||||
/// at some point in the chain of decoration, there must be a concrete shape
|
||||
/// (specifically, a concrete IShape that was createad without having another
|
||||
/// IShape passed to it)
|
||||
/// </summary>
|
||||
/// <param name="decorated"></param>
|
||||
public ShapeDecorator(IShape decorated)
|
||||
{
|
||||
_decorated = decorated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marking this abstract satisfies the implementation of IShape,
|
||||
/// but forces inheriting classes to implement their own drawing logic
|
||||
/// </summary>
|
||||
public abstract void Draw();
|
||||
}
|
||||
6
patterns/decorator/ShapeDecorations/Shapes/IShape.cs
Normal file
6
patterns/decorator/ShapeDecorations/Shapes/IShape.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ShapeDecorations.Shapes;
|
||||
|
||||
public interface IShape
|
||||
{
|
||||
void Draw();
|
||||
}
|
||||
15
patterns/decorator/ShapeDecorations/Shapes/Rectangle.cs
Normal file
15
patterns/decorator/ShapeDecorations/Shapes/Rectangle.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace ShapeDecorations.Shapes;
|
||||
|
||||
public class Rectangle : IShape
|
||||
{
|
||||
public void Draw()
|
||||
{
|
||||
Console.WriteLine("Drawing a Rectangle");
|
||||
}
|
||||
}
|
||||
14
patterns/facade/CarEngineFacade/AirFlowController.cs
Normal file
14
patterns/facade/CarEngineFacade/AirFlowController.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class AirFlowController
|
||||
{
|
||||
public void TakeAir()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
8
patterns/facade/CarEngineFacade/CarEngineFacade.csproj
Normal file
8
patterns/facade/CarEngineFacade/CarEngineFacade.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>CarEngineFacade</RootNamespace>
|
||||
<AssemblyName>CarEngineFacade</AssemblyName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
14
patterns/facade/CarEngineFacade/CatalyticConverter.cs
Normal file
14
patterns/facade/CarEngineFacade/CatalyticConverter.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class CatalyticConverter
|
||||
{
|
||||
public void On()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
19
patterns/facade/CarEngineFacade/CoolingController.cs
Normal file
19
patterns/facade/CarEngineFacade/CoolingController.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class CoolingController
|
||||
{
|
||||
public void Run()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Cool()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
29
patterns/facade/CarEngineFacade/Facade.cs
Normal file
29
patterns/facade/CarEngineFacade/Facade.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class Facade
|
||||
{
|
||||
private readonly AirFlowController _air = new();
|
||||
private readonly CatalyticConverter _cat = new();
|
||||
private readonly CoolingController _cool = new();
|
||||
private readonly FuelInjector _fuel = new();
|
||||
private readonly Starter _start = new();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_air.TakeAir();
|
||||
_fuel.On();
|
||||
_fuel.Inject();
|
||||
_start.Start();
|
||||
_cool.Run();
|
||||
_cat.On();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_fuel.Off();
|
||||
_cat.Off();
|
||||
_cool.Cool();
|
||||
_cool.Stop();
|
||||
_air.Off();
|
||||
}
|
||||
}
|
||||
19
patterns/facade/CarEngineFacade/FuelInjector.cs
Normal file
19
patterns/facade/CarEngineFacade/FuelInjector.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class FuelInjector
|
||||
{
|
||||
public void On()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Inject()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
38
patterns/facade/CarEngineFacade/Program.cs
Normal file
38
patterns/facade/CarEngineFacade/Program.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
//// without the facade, starting the engine involves creating all the objects
|
||||
//var airflow = new AirFlowController();
|
||||
//var fuel = new FuelInjector();
|
||||
//var start = new Starter();
|
||||
//var cool = new CoolingController();
|
||||
//var cat = new CatalyticConverter();
|
||||
|
||||
//// then performing all the steps in the correct order
|
||||
//airflow.TakeAir();
|
||||
//fuel.On();
|
||||
//fuel.Inject();
|
||||
//start.Start();
|
||||
//cool.Run();
|
||||
//cat.On();
|
||||
|
||||
//// and shut down also requires steps in the correct order
|
||||
//fuel.Off();
|
||||
//cat.Off();
|
||||
//cool.Cool();
|
||||
//cool.Stop();
|
||||
//airflow.Off();
|
||||
|
||||
// with the Facade, all creation is handled internally
|
||||
var facade = new Facade();
|
||||
|
||||
// as is starting
|
||||
facade.Start();
|
||||
|
||||
// and stopping
|
||||
facade.Stop();
|
||||
}
|
||||
}
|
||||
9
patterns/facade/CarEngineFacade/Starter.cs
Normal file
9
patterns/facade/CarEngineFacade/Starter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CarEngineFacade;
|
||||
|
||||
public class Starter
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class AirFlowController
|
||||
{
|
||||
public void TakeAir()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class CatalyticConverter
|
||||
{
|
||||
public void On()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class CoolingController
|
||||
{
|
||||
public void Run()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Cool()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
29
patterns/facade/CarEngineFacadeRecording/Facade.cs
Normal file
29
patterns/facade/CarEngineFacadeRecording/Facade.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class Facade
|
||||
{
|
||||
private readonly AirFlowController _air = new();
|
||||
private readonly CatalyticConverter _cat = new();
|
||||
private readonly CoolingController _cool = new();
|
||||
private readonly FuelInjector _fuel = new();
|
||||
private readonly Starter _start = new();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_air.TakeAir();
|
||||
_fuel.On();
|
||||
_fuel.Inject();
|
||||
_start.Start();
|
||||
_cool.Run();
|
||||
_cat.On();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_fuel.Off();
|
||||
_cat.Off();
|
||||
_cool.Cool();
|
||||
_cool.Stop();
|
||||
_air.Off();
|
||||
}
|
||||
}
|
||||
19
patterns/facade/CarEngineFacadeRecording/FuelInjector.cs
Normal file
19
patterns/facade/CarEngineFacadeRecording/FuelInjector.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class FuelInjector
|
||||
{
|
||||
public void On()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Inject()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void Off()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
36
patterns/facade/CarEngineFacadeRecording/Program.cs
Normal file
36
patterns/facade/CarEngineFacadeRecording/Program.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
#region
|
||||
|
||||
using CarEngineFacadeRecording;
|
||||
|
||||
#endregion
|
||||
|
||||
//// without the facade, starting the engine involves creating all the objects
|
||||
//var airflow = new AirFlowController();
|
||||
//var fuel = new FuelInjector();
|
||||
//var start = new Starter();
|
||||
//var cool = new CoolingController();
|
||||
//var cat = new CatalyticConverter();
|
||||
|
||||
//// then performing all the steps in the correct order
|
||||
//airflow.TakeAir();
|
||||
//fuel.On();
|
||||
//fuel.Inject();
|
||||
//start.Start();
|
||||
//cool.Run();
|
||||
//cat.On();
|
||||
|
||||
//// and shut down also requires steps in the correct order
|
||||
//fuel.Off();
|
||||
//cat.Off();
|
||||
//cool.Cool();
|
||||
//cool.Stop();
|
||||
//airflow.Off();
|
||||
|
||||
// with the Facade, all creation is handled internally
|
||||
var facade = new Facade();
|
||||
|
||||
// as is starting
|
||||
facade.Start();
|
||||
|
||||
// and stopping
|
||||
facade.Stop();
|
||||
9
patterns/facade/CarEngineFacadeRecording/Starter.cs
Normal file
9
patterns/facade/CarEngineFacadeRecording/Starter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CarEngineFacadeRecording;
|
||||
|
||||
public class Starter
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>AirTrafficControl</RootNamespace>
|
||||
<AssemblyName>AirTrafficControl</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
363
patterns/mediator/AirTrafficControl/Program.cs
Normal file
363
patterns/mediator/AirTrafficControl/Program.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace AirTrafficControl;
|
||||
|
||||
public enum AirTrafficRequest
|
||||
{
|
||||
LandingRequest,
|
||||
TakeoffRequest,
|
||||
RunwayCrossingRequest,
|
||||
LandingRequestDone,
|
||||
TakeoffRequestDone,
|
||||
RunwayCrossingRequestDone,
|
||||
}
|
||||
|
||||
public interface IColleague
|
||||
{
|
||||
void ReceiveMessage(string instruction, bool accepted);
|
||||
}
|
||||
|
||||
public interface IMediator
|
||||
{
|
||||
void Notify(IColleague sender, AirTrafficRequest request);
|
||||
}
|
||||
|
||||
public class Flight : IColleague
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public string FlightNumber { get; }
|
||||
|
||||
public Flight(string flightNumber, IMediator mediator)
|
||||
{
|
||||
FlightNumber = flightNumber;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public void RequestLanding()
|
||||
{
|
||||
Console.WriteLine($"Flight {FlightNumber} requesting landing.");
|
||||
_mediator.Notify(this, AirTrafficRequest.LandingRequest);
|
||||
}
|
||||
|
||||
public void RequestTakeoff()
|
||||
{
|
||||
Console.WriteLine($"Flight {FlightNumber} requesting takeoff.");
|
||||
_mediator.Notify(this, AirTrafficRequest.TakeoffRequest);
|
||||
}
|
||||
|
||||
public void LandingCompleted()
|
||||
{
|
||||
Console.WriteLine($"Flight {FlightNumber} has completed landing.");
|
||||
_mediator.Notify(this, AirTrafficRequest.LandingRequestDone);
|
||||
}
|
||||
|
||||
public void TakeoffCompleted()
|
||||
{
|
||||
Console.WriteLine($"Flight {FlightNumber} has completed takeoff.");
|
||||
_mediator.Notify(this, AirTrafficRequest.TakeoffRequestDone);
|
||||
}
|
||||
|
||||
public void ReceiveMessage(string instruction, bool accepted)
|
||||
{
|
||||
if (accepted)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Flight {FlightNumber} received instruction: {instruction}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Flight {FlightNumber} received rejection for instruction: {instruction}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GroundVehicle : IColleague
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public string VehicleId { get; }
|
||||
|
||||
public GroundVehicle(string vehicleId, IMediator mediator)
|
||||
{
|
||||
VehicleId = vehicleId;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public void RequestRunwayCrossing()
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Ground vehicle {VehicleId} requesting runway crossing."
|
||||
);
|
||||
_mediator.Notify(this, AirTrafficRequest.RunwayCrossingRequest);
|
||||
}
|
||||
|
||||
public void RunwayCrossingCompleted()
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Ground vehicle {VehicleId} has completed runway crossing."
|
||||
);
|
||||
_mediator.Notify(this, AirTrafficRequest.RunwayCrossingRequestDone);
|
||||
}
|
||||
|
||||
public void ReceiveMessage(string instruction, bool accepted)
|
||||
{
|
||||
if (accepted)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Ground vehicle {VehicleId} received instruction: {instruction}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Ground vehicle {VehicleId} received rejection for instruction: {instruction}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AirTrafficControlTower : IMediator
|
||||
{
|
||||
private class Runway
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public IColleague? OccupiedBy { get; set; }
|
||||
|
||||
public Runway(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Runway[] _runways = new Runway[30];
|
||||
|
||||
public AirTrafficControlTower()
|
||||
{
|
||||
// create runways
|
||||
for (int i = 0; i < _runways.Length; i++)
|
||||
{
|
||||
_runways[i] = new Runway($"Runway R{i + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify(IColleague sender, AirTrafficRequest request)
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case AirTrafficRequest.LandingRequest:
|
||||
HandleLandingRequest(sender);
|
||||
break;
|
||||
case AirTrafficRequest.TakeoffRequest:
|
||||
HandleTakeoffRequest(sender);
|
||||
break;
|
||||
case AirTrafficRequest.RunwayCrossingRequest:
|
||||
HandleRunwayCrossingRequest(sender);
|
||||
break;
|
||||
case AirTrafficRequest.LandingRequestDone:
|
||||
HandleLandingRequestDone(sender);
|
||||
break;
|
||||
case AirTrafficRequest.TakeoffRequestDone:
|
||||
HandleTakeoffRequestDone(sender);
|
||||
break;
|
||||
case AirTrafficRequest.RunwayCrossingRequestDone:
|
||||
HandleRunwayCrossingRequestDone(sender);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleLandingRequest(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy is null);
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = sender;
|
||||
sender.ReceiveMessage(
|
||||
$"Landing approved on {val.Name}.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"No available runways for landing.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTakeoffRequest(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy is null);
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = sender;
|
||||
sender.ReceiveMessage(
|
||||
$"Takeoff approved on {val.Name}.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"No available runways for takeoff.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleLandingRequestDone(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy == sender);
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = null;
|
||||
sender.ReceiveMessage(
|
||||
$"You have cleared {val.Name} after landing.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"You were not occupying any runway.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTakeoffRequestDone(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy == sender);
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = null;
|
||||
sender.ReceiveMessage(
|
||||
$"You have cleared {val.Name} after takeoff.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"You were not occupying any runway.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRunwayCrossingRequestDone(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy == sender);
|
||||
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = null;
|
||||
sender.ReceiveMessage(
|
||||
$"You have cleared {val.Name} after crossing.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"You were not occupying any runway.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRunwayCrossingRequest(IColleague sender)
|
||||
{
|
||||
Runway? val = _runways.FirstOrDefault(r => r.OccupiedBy is null);
|
||||
|
||||
if (val is not null)
|
||||
{
|
||||
val.OccupiedBy = sender;
|
||||
sender.ReceiveMessage(
|
||||
$"Crossing approved on {val.Name}.",
|
||||
accepted: true
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.ReceiveMessage(
|
||||
"No available runways for crossing.",
|
||||
accepted: false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
var tower = new AirTrafficControlTower();
|
||||
|
||||
Flight[] flights = new Flight[1000];
|
||||
|
||||
for (int i = 0; i < flights.Length; i++)
|
||||
{
|
||||
flights[i] = new Flight($"F{i + 1:D4}", tower);
|
||||
}
|
||||
|
||||
GroundVehicle[] vehicles = new GroundVehicle[10];
|
||||
|
||||
for (int i = 0; i < vehicles.Length; i++)
|
||||
{
|
||||
vehicles[i] = new GroundVehicle($"V{i + 1:D3}", tower);
|
||||
}
|
||||
|
||||
// create simulation with random events
|
||||
Random rand = new Random();
|
||||
|
||||
ulong tick = 0;
|
||||
|
||||
while (tick < 100_000)
|
||||
{
|
||||
tick++;
|
||||
|
||||
// randomly select a flight or vehicle to make a request
|
||||
int index = rand.Next(0, flights.Length + vehicles.Length);
|
||||
if (index < flights.Length)
|
||||
{
|
||||
Flight flight = flights[index];
|
||||
int action = rand.Next(0, 4);
|
||||
switch (action)
|
||||
{
|
||||
case 0:
|
||||
flight.RequestLanding();
|
||||
break;
|
||||
case 1:
|
||||
flight.RequestTakeoff();
|
||||
break;
|
||||
case 2:
|
||||
flight.LandingCompleted();
|
||||
break;
|
||||
case 3:
|
||||
flight.TakeoffCompleted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GroundVehicle vehicle = vehicles[index - flights.Length];
|
||||
int action = rand.Next(0, 2);
|
||||
switch (action)
|
||||
{
|
||||
case 0:
|
||||
vehicle.RequestRunwayCrossing();
|
||||
break;
|
||||
case 1:
|
||||
vehicle.RunwayCrossingCompleted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
patterns/memento/MementoPattern/Caretaker.cs
Normal file
24
patterns/memento/MementoPattern/Caretaker.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace MementoPattern;
|
||||
|
||||
public class Caretaker
|
||||
{
|
||||
//container that could hold memento of Originator
|
||||
//object
|
||||
private readonly Stack<IMemento> _mementos = new();
|
||||
private readonly IOriginator _originator;
|
||||
|
||||
public Caretaker(IOriginator originator)
|
||||
{
|
||||
_originator = originator;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_mementos.Push(_originator.GetMemento());
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_originator.SetMemento(_mementos.Pop());
|
||||
}
|
||||
}
|
||||
3
patterns/memento/MementoPattern/IMemento.cs
Normal file
3
patterns/memento/MementoPattern/IMemento.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace MementoPattern;
|
||||
|
||||
public interface IMemento { }
|
||||
7
patterns/memento/MementoPattern/IOriginator.cs
Normal file
7
patterns/memento/MementoPattern/IOriginator.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace MementoPattern;
|
||||
|
||||
public interface IOriginator
|
||||
{
|
||||
IMemento GetMemento();
|
||||
void SetMemento(IMemento memento);
|
||||
}
|
||||
8
patterns/memento/MementoPattern/MementoPattern.csproj
Normal file
8
patterns/memento/MementoPattern/MementoPattern.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
46
patterns/memento/MementoPattern/Originator.cs
Normal file
46
patterns/memento/MementoPattern/Originator.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace MementoPattern;
|
||||
|
||||
public class Originator : IOriginator
|
||||
{
|
||||
private Guid _id;
|
||||
|
||||
public Originator()
|
||||
{
|
||||
_id = Guid.NewGuid();
|
||||
Console.WriteLine($"Starting ID: {_id}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public IMemento GetMemento()
|
||||
{
|
||||
Memento memento = new Memento();
|
||||
memento.Id = _id;
|
||||
Console.WriteLine("Creating Memento...");
|
||||
Console.WriteLine();
|
||||
return memento;
|
||||
}
|
||||
|
||||
public void SetMemento(IMemento memento)
|
||||
{
|
||||
if (!(memento is Memento))
|
||||
return;
|
||||
Console.WriteLine("Restting ID...");
|
||||
_id = (memento as Memento).Id;
|
||||
|
||||
Console.WriteLine($"ID is reset to {_id}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public void UpdateId()
|
||||
{
|
||||
Console.WriteLine("Updating Id...");
|
||||
_id = Guid.NewGuid();
|
||||
Console.WriteLine($"Updated ID is now {_id}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private class Memento : IMemento
|
||||
{
|
||||
public Guid Id;
|
||||
}
|
||||
}
|
||||
45
patterns/memento/MementoPattern/Program.cs
Normal file
45
patterns/memento/MementoPattern/Program.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace MementoPattern;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
Originator originator = new Originator();
|
||||
Caretaker caretaker = new Caretaker(originator);
|
||||
|
||||
caretaker.Save();
|
||||
Console.WriteLine(
|
||||
"After the first save, the originator's first ID is saved as a memento and pushed on the caretaker's stack. Enter any input to continue."
|
||||
);
|
||||
Console.ReadLine();
|
||||
|
||||
originator.UpdateId();
|
||||
originator.UpdateId();
|
||||
|
||||
caretaker.Save();
|
||||
Console.WriteLine(
|
||||
"After two updates and a save, only the second update is saved as a memento and pushed on the caretaker's stack. Enter any input to continue."
|
||||
);
|
||||
Console.ReadLine();
|
||||
|
||||
originator.UpdateId();
|
||||
caretaker.Undo();
|
||||
|
||||
Console.WriteLine(
|
||||
"After this latest update, since we called Undo() the ID will be changed from the latest updated ID to the prior one (i.e. the latest one that was actually saved to the stack). This is the main point of memento. Enter any input to continue."
|
||||
);
|
||||
Console.ReadLine();
|
||||
|
||||
originator.UpdateId();
|
||||
caretaker.Save();
|
||||
|
||||
Console.WriteLine(
|
||||
"A new ID is updated and pushed onto the stack. Enter any input to continue."
|
||||
);
|
||||
Console.ReadLine();
|
||||
|
||||
originator.UpdateId();
|
||||
caretaker.Undo();
|
||||
caretaker.Undo();
|
||||
}
|
||||
}
|
||||
94
patterns/observer/ObserverPattern/Program.cs
Normal file
94
patterns/observer/ObserverPattern/Program.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace UpdatedOne;
|
||||
|
||||
public interface IObserver<TSubject>
|
||||
{
|
||||
void Update(TSubject subject);
|
||||
}
|
||||
|
||||
public abstract class Subject<TSubject>
|
||||
{
|
||||
private readonly List<IObserver<TSubject>> _observers = new();
|
||||
|
||||
public void Subscribe(IObserver<TSubject> observer)
|
||||
{
|
||||
if (!_observers.Contains(observer))
|
||||
_observers.Add(observer);
|
||||
}
|
||||
|
||||
public void Unsubscribe(IObserver<TSubject> observer)
|
||||
{
|
||||
_observers.Remove(observer);
|
||||
}
|
||||
|
||||
protected void Notify(TSubject subject)
|
||||
{
|
||||
foreach (IObserver<TSubject> observer in _observers.ToArray())
|
||||
observer.Update(subject);
|
||||
}
|
||||
}
|
||||
|
||||
public class Data : Subject<Data>
|
||||
{
|
||||
private int _value;
|
||||
|
||||
public Data(int value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public void SetData(int value)
|
||||
{
|
||||
if (_value == value)
|
||||
return;
|
||||
|
||||
_value = value;
|
||||
|
||||
Console.WriteLine($"Data changed to {_value}");
|
||||
|
||||
Notify(this);
|
||||
}
|
||||
|
||||
public int GetData()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public class BarGraph : IObserver<Data>
|
||||
{
|
||||
public void Update(Data subject)
|
||||
{
|
||||
int value = subject.GetData();
|
||||
|
||||
Console.WriteLine($"Bar Graph updated: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
public class PieChart : IObserver<Data>
|
||||
{
|
||||
public void Update(Data subject)
|
||||
{
|
||||
int value = subject.GetData();
|
||||
|
||||
Console.WriteLine($"Pie Chart updated: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static void Main()
|
||||
{
|
||||
Data data = new Data(2);
|
||||
|
||||
BarGraph barGraph = new BarGraph();
|
||||
PieChart pieChart = new PieChart();
|
||||
|
||||
data.Subscribe(barGraph);
|
||||
data.Subscribe(pieChart);
|
||||
|
||||
data.SetData(3);
|
||||
|
||||
data.Unsubscribe(barGraph);
|
||||
data.Unsubscribe(pieChart);
|
||||
}
|
||||
}
|
||||
8
patterns/observer/ObserverPattern/UpdatedOne.csproj
Normal file
8
patterns/observer/ObserverPattern/UpdatedOne.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
6
patterns/state/BadCarState/BadCarState.csproj
Normal file
6
patterns/state/BadCarState/BadCarState.csproj
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
81
patterns/state/BadCarState/Car.cs
Normal file
81
patterns/state/BadCarState/Car.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace BadCarState;
|
||||
|
||||
public class Car
|
||||
{
|
||||
private State _state = State.Off;
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "turn-clockwise":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Off:
|
||||
Console.WriteLine("The engine has started");
|
||||
Console.WriteLine();
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
case State.Stopped:
|
||||
case State.Driving:
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "turn-counter":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The engine has stopped");
|
||||
Console.WriteLine();
|
||||
_state = State.Off;
|
||||
break;
|
||||
case State.Driving:
|
||||
Console.WriteLine(
|
||||
"The engine turns off, and the car stops suddenly"
|
||||
);
|
||||
Console.WriteLine();
|
||||
_state = State.Off;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "gas":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The car begins to drive");
|
||||
Console.WriteLine();
|
||||
_state = State.Driving;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "brake":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Driving:
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
Console.WriteLine();
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "exit":
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
Off,
|
||||
Stopped,
|
||||
Driving,
|
||||
}
|
||||
}
|
||||
41
patterns/state/BadCarState/Program.cs
Normal file
41
patterns/state/BadCarState/Program.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace BadCarState;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var car = new Car();
|
||||
|
||||
string input;
|
||||
bool keepRunning = true;
|
||||
|
||||
Console.WriteLine("Welcome to the Driving Simulater");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" turn-clockwise -Turns the car's key clockwise");
|
||||
Console.WriteLine(
|
||||
" turn-counter -Turns the car's key counter clockwise"
|
||||
);
|
||||
Console.WriteLine(" gas -Presses the gas pedal");
|
||||
Console.WriteLine(" brake -Presses the brake pedal");
|
||||
Console.WriteLine(" exit -Exits the simulator");
|
||||
Console.WriteLine();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("> ");
|
||||
input = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
keepRunning = car.HandleInput(input);
|
||||
} while (keepRunning);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("goodbye");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
44
patterns/state/CarState/Car.cs
Normal file
44
patterns/state/CarState/Car.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region
|
||||
|
||||
using CarState.States;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CarState;
|
||||
|
||||
public class Car
|
||||
{
|
||||
private State _state;
|
||||
|
||||
public Car()
|
||||
{
|
||||
ChangeState(new Off(this));
|
||||
}
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "turn-clockwise":
|
||||
_state.TurnKeyClockwise();
|
||||
break;
|
||||
case "turn-counter":
|
||||
_state.TurnKeyCounterClockwise();
|
||||
break;
|
||||
case "gas":
|
||||
_state.PressGas();
|
||||
break;
|
||||
case "brake":
|
||||
_state.PressBrake();
|
||||
break;
|
||||
case "exit":
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ChangeState(State state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
}
|
||||
6
patterns/state/CarState/CarState.csproj
Normal file
6
patterns/state/CarState/CarState.csproj
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
41
patterns/state/CarState/Program.cs
Normal file
41
patterns/state/CarState/Program.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CarState;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var car = new Car();
|
||||
|
||||
string input;
|
||||
bool keepRunning = true;
|
||||
|
||||
Console.WriteLine("Welcome to the Driving Simulater");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" turn-clockwise -Turns the car's key clockwise");
|
||||
Console.WriteLine(
|
||||
" turn-counter -Turns the car's key counter clockwise"
|
||||
);
|
||||
Console.WriteLine(" gas -Presses the gas pedal");
|
||||
Console.WriteLine(" brake -Presses the brake pedal");
|
||||
Console.WriteLine(" exit -Exits the simulator");
|
||||
Console.WriteLine();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("> ");
|
||||
input = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
keepRunning = car.HandleInput(input);
|
||||
} while (keepRunning);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("goodbye");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
33
patterns/state/CarState/States/Driving.cs
Normal file
33
patterns/state/CarState/States/Driving.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CarState.States;
|
||||
|
||||
public class Driving : State
|
||||
{
|
||||
public Driving(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine turns off, and the car stops suddenly");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
|
||||
public override void PressBrake()
|
||||
{
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Stopped(_car));
|
||||
}
|
||||
}
|
||||
20
patterns/state/CarState/States/Off.cs
Normal file
20
patterns/state/CarState/States/Off.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CarState.States;
|
||||
|
||||
public class Off : State
|
||||
{
|
||||
public Off(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has started");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Stopped(_car));
|
||||
}
|
||||
}
|
||||
19
patterns/state/CarState/States/State.cs
Normal file
19
patterns/state/CarState/States/State.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace CarState.States;
|
||||
|
||||
public class State
|
||||
{
|
||||
protected Car _car;
|
||||
|
||||
public State(Car car)
|
||||
{
|
||||
_car = car;
|
||||
}
|
||||
|
||||
public virtual void TurnKeyClockwise() { }
|
||||
|
||||
public virtual void TurnKeyCounterClockwise() { }
|
||||
|
||||
public virtual void PressGas() { }
|
||||
|
||||
public virtual void PressBrake() { }
|
||||
}
|
||||
33
patterns/state/CarState/States/Stopped.cs
Normal file
33
patterns/state/CarState/States/Stopped.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CarState.States;
|
||||
|
||||
public class Stopped : State
|
||||
{
|
||||
public Stopped(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void PressGas()
|
||||
{
|
||||
Console.WriteLine("The car begins to drive");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Driving(_car));
|
||||
}
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has stopped");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
}
|
||||
44
patterns/state/WithStatePattern/Car.cs
Normal file
44
patterns/state/WithStatePattern/Car.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region
|
||||
|
||||
using WithStatePattern.States;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace WithStatePattern;
|
||||
|
||||
public class Car
|
||||
{
|
||||
private State _state;
|
||||
|
||||
public Car()
|
||||
{
|
||||
ChangeState(new Off(this));
|
||||
}
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "turn-clockwise":
|
||||
_state.TurnKeyClockwise();
|
||||
break;
|
||||
case "turn-counter":
|
||||
_state.TurnKeyCounterClockwise();
|
||||
break;
|
||||
case "gas":
|
||||
_state.PressGas();
|
||||
break;
|
||||
case "brake":
|
||||
_state.PressBrake();
|
||||
break;
|
||||
case "exit":
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ChangeState(State state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
}
|
||||
35
patterns/state/WithStatePattern/Program.cs
Normal file
35
patterns/state/WithStatePattern/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace WithStatePattern;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var car = new Car();
|
||||
|
||||
string input;
|
||||
bool keepRunning = true;
|
||||
|
||||
Console.WriteLine("Welcome to the Driving Simulater");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" turn-clockwise -Turns the car's key clockwise");
|
||||
Console.WriteLine(
|
||||
" turn-counter -Turns the car's key counter clockwise"
|
||||
);
|
||||
Console.WriteLine(" gas -Presses the gas pedal");
|
||||
Console.WriteLine(" brake -Presses the brake pedal");
|
||||
Console.WriteLine(" exit -Exits the simulator");
|
||||
Console.WriteLine();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("> ");
|
||||
input = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
keepRunning = car.HandleInput(input);
|
||||
} while (keepRunning);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("goodbye");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
27
patterns/state/WithStatePattern/States/Driving.cs
Normal file
27
patterns/state/WithStatePattern/States/Driving.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace WithStatePattern.States;
|
||||
|
||||
public class Driving : State
|
||||
{
|
||||
public Driving(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine turns off, and the car stops suddenly");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
|
||||
public override void PressBrake()
|
||||
{
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Stopped(_car));
|
||||
}
|
||||
}
|
||||
14
patterns/state/WithStatePattern/States/Off.cs
Normal file
14
patterns/state/WithStatePattern/States/Off.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace WithStatePattern.States;
|
||||
|
||||
public class Off : State
|
||||
{
|
||||
public Off(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has started");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Stopped(_car));
|
||||
}
|
||||
}
|
||||
19
patterns/state/WithStatePattern/States/State.cs
Normal file
19
patterns/state/WithStatePattern/States/State.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace WithStatePattern.States;
|
||||
|
||||
public class State
|
||||
{
|
||||
protected Car _car;
|
||||
|
||||
public State(Car car)
|
||||
{
|
||||
_car = car;
|
||||
}
|
||||
|
||||
public virtual void TurnKeyClockwise() { }
|
||||
|
||||
public virtual void TurnKeyCounterClockwise() { }
|
||||
|
||||
public virtual void PressGas() { }
|
||||
|
||||
public virtual void PressBrake() { }
|
||||
}
|
||||
27
patterns/state/WithStatePattern/States/Stopped.cs
Normal file
27
patterns/state/WithStatePattern/States/Stopped.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace WithStatePattern.States;
|
||||
|
||||
public class Stopped : State
|
||||
{
|
||||
public Stopped(Car car)
|
||||
: base(car) { }
|
||||
|
||||
public override void PressGas()
|
||||
{
|
||||
Console.WriteLine("The car begins to drive");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Driving(_car));
|
||||
}
|
||||
|
||||
public override void TurnKeyClockwise()
|
||||
{
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public override void TurnKeyCounterClockwise()
|
||||
{
|
||||
Console.WriteLine("The engine has stopped");
|
||||
Console.WriteLine();
|
||||
_car.ChangeState(new Off(_car));
|
||||
}
|
||||
}
|
||||
8
patterns/state/WithStatePattern/WithStatePattern.csproj
Normal file
8
patterns/state/WithStatePattern/WithStatePattern.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
75
patterns/state/WithoutStatePattern/Car.cs
Normal file
75
patterns/state/WithoutStatePattern/Car.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace WithoutStatePattern;
|
||||
|
||||
public class Car
|
||||
{
|
||||
private State _state = State.Off;
|
||||
|
||||
public bool HandleInput(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "turn-clockwise":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Off:
|
||||
Console.WriteLine("The engine has started");
|
||||
Console.WriteLine();
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
case State.Stopped:
|
||||
case State.Driving:
|
||||
Console.WriteLine("You hear a grinding noise...");
|
||||
Console.WriteLine();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "turn-counter":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The engine has stopped");
|
||||
Console.WriteLine();
|
||||
_state = State.Off;
|
||||
break;
|
||||
case State.Driving:
|
||||
Console.WriteLine(
|
||||
"The engine turns off, and the car stops suddenly"
|
||||
);
|
||||
Console.WriteLine();
|
||||
_state = State.Off;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "gas":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Stopped:
|
||||
Console.WriteLine("The car begins to drive");
|
||||
Console.WriteLine();
|
||||
_state = State.Driving;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "brake":
|
||||
switch (_state)
|
||||
{
|
||||
case State.Driving:
|
||||
Console.WriteLine("The car slows to a stop");
|
||||
Console.WriteLine();
|
||||
_state = State.Stopped;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "exit":
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
Off,
|
||||
Stopped,
|
||||
Driving,
|
||||
}
|
||||
}
|
||||
35
patterns/state/WithoutStatePattern/Program.cs
Normal file
35
patterns/state/WithoutStatePattern/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace WithoutStatePattern;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var car = new Car();
|
||||
|
||||
string input;
|
||||
bool keepRunning = true;
|
||||
|
||||
Console.WriteLine("Welcome to the Driving Simulater");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" turn-clockwise -Turns the car's key clockwise");
|
||||
Console.WriteLine(
|
||||
" turn-counter -Turns the car's key counter clockwise"
|
||||
);
|
||||
Console.WriteLine(" gas -Presses the gas pedal");
|
||||
Console.WriteLine(" brake -Presses the brake pedal");
|
||||
Console.WriteLine(" exit -Exits the simulator");
|
||||
Console.WriteLine();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("> ");
|
||||
input = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
keepRunning = car.HandleInput(input);
|
||||
} while (keepRunning);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("goodbye");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user