Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions Core/Extensions/ConsoleExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using Core.Console.Interfaces;
using Core.Console.Interfaces;
using Microsoft.Extensions.Logging;

namespace Core.Extensions;
Expand All @@ -8,17 +7,6 @@ internal static class ConsoleExtensions
{
public static ILoggerFactory GetLoggerFactory() => LoggerFactory.Create(builder => { builder.AddConsole(); });

public static void Log<T>(this IConsoleLogger logger, string identifier, IEnumerable<T> options)
{
var message = new StringBuilder($"Please enter from following {identifier}: ");

var i = 0;
foreach (var option in options)
message.Append($"\n {++i}. {option} ");

logger.Log(message.ToString());
}

public static TEnum AcceptInputEnum<TEnum>(this IInputReader inputReader, TEnum defaultValue)
{
var input = inputReader.AcceptInput();
Expand All @@ -31,4 +19,9 @@ public static decimal AcceptInputDecimal(this IInputReader inputReader)
var input = inputReader.AcceptInput();
return decimal.Parse(input);
}

public static void LogInfoQuit(this IConsoleLogger logger)
{
logger.Log("Please provide 'quit' input in order to quit from the example.");
}
}
33 changes: 17 additions & 16 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<NoWarn>NU1507</NoWarn>
</PropertyGroup>
<ItemGroup Label="Microsoft Nugets">
<PackageVersion Include="coverlet.collector" Version="3.1.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.2.0" />
</ItemGroup>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<NoWarn>NU1507</NoWarn>
</PropertyGroup>
<ItemGroup Label="Microsoft Nugets">
<PackageVersion Include="coverlet.collector" Version="3.1.2" />
<PackageVersion Include="Microsoft.AspNet.WebApi.Client" Version="4.0.20505" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.2.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace GofConsoleApp.Examples.Behavioral.ObserverPattern.Components;

internal enum EnumTopic
{
Sports,
Politics,
Weather,
Holidays,
Invalid,
Quit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Core.Console.Interfaces;
using GofPatterns.Behavioral.ObserverPattern.Interfaces;

namespace GofConsoleApp.Examples.Behavioral.ObserverPattern.Components;

internal class PersonAndrewSmith : ISubscriber<string>
{
private readonly IConsoleLogger logger;

public PersonAndrewSmith(IConsoleLogger logger)
{
this.logger = logger;
}

public void Update(string input)
{
logger.Log($"Andrew Smith received: {input}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Core.Console.Interfaces;
using GofPatterns.Behavioral.ObserverPattern.Interfaces;

namespace GofConsoleApp.Examples.Behavioral.ObserverPattern.Components;

internal class PersonJohnDoe : ISubscriber<string>
{
private readonly IConsoleLogger logger;

public PersonJohnDoe(IConsoleLogger logger)
{
this.logger = logger;
}

public void Update(string input)
{
logger.Log($"John Doe received: {input}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Core.Extensions;
using GofConsoleApp.Examples.Behavioral.ObserverPattern.Components;
using GofPatterns.Behavioral.ObserverPattern;
using GofPatterns.Behavioral.ObserverPattern.Interfaces;

namespace GofConsoleApp.Examples.Behavioral.ObserverPattern;

internal class ObserverPatternExample : BaseExample
{
private readonly IPublisher<string> newsPublisher = new Publisher<string>();

protected override bool Execute()
{
var johnDoe = new PersonJohnDoe(Logger);
var andrewSmith = new PersonAndrewSmith(Logger);

newsPublisher.AddSubscriber(johnDoe);
newsPublisher.AddSubscriber(andrewSmith);

while (true)
{
Logger.LogInfoQuit();

var newsUpdate = AcceptInputString("news update");

if (newsUpdate.Trim().ToLower().Equals("quit"))
break;

newsPublisher.NotifySubscribers(newsUpdate);
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Core.Extensions;
using GofConsoleApp.Examples.Behavioral.ObserverPattern.Components;
using GofPatterns.Behavioral.ObserverPattern;
using GofPatterns.Behavioral.ObserverPattern.Interfaces;

namespace GofConsoleApp.Examples.Behavioral.ObserverPattern;

internal class ObserverPatternExampleWithCategory : BaseExample
{
private readonly IPublisher<string, EnumTopic> newsPublisher = new Publisher<string, EnumTopic>();

protected override bool Execute()
{
var johnDoe = new PersonJohnDoe(Logger);
var andrewSmith = new PersonAndrewSmith(Logger);

newsPublisher.AddSubscriber(johnDoe, EnumTopic.Sports);
newsPublisher.AddSubscriber(andrewSmith, EnumTopic.Sports);
newsPublisher.AddSubscriber(johnDoe, EnumTopic.Politics);
newsPublisher.AddSubscriber(andrewSmith, EnumTopic.Weather);
newsPublisher.AddSubscriber(johnDoe, EnumTopic.Holidays);

do
{
Logger.LogInfoQuit();

var topic = AcceptInputEnum(EnumTopic.Invalid, nameof(EnumTopic), EnumTopic.Invalid);

if (IsInvalidOrQuit(topic, EnumTopic.Invalid, EnumTopic.Quit, out _))
return true;

var newsUpdate = AcceptInputString($"{topic} news update");

newsPublisher.NotifySubscribers(newsUpdate, topic);

} while (true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ protected override bool Execute()

do
{
var inputOption = AcceptInputEnum(Invalid, "state", Invalid);
var state = AcceptInputEnum(Invalid, "state", Invalid);

if (IsInvalidOrQuit(inputOption, Invalid, Quit, out var output))
if (IsInvalidOrQuit(state, Invalid, Quit, out var output))
return output;

if (bulb.State.Name.Equals(inputOption.ToString()))
if (bulb.State.Name.Equals(state.ToString()))
{
Logger.Log($"Bulb already in {bulb.State.Name} state.");
continue;
}

switch (inputOption)
switch (state)
{
case On:
bulb.SetState(on);
Expand Down
13 changes: 13 additions & 0 deletions GofConsoleApp/Examples/ExecutionHelpers/PatternOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using GofConsoleApp.Examples.Behavioral.CommandPattern;
using GofConsoleApp.Examples.Behavioral.CorPattern;
using GofConsoleApp.Examples.Behavioral.MediatorPattern;
using GofConsoleApp.Examples.Behavioral.ObserverPattern;
using GofConsoleApp.Examples.Behavioral.StatePattern;
using GofConsoleApp.Examples.Behavioral.StrategyPattern;
using GofConsoleApp.Examples.Creational.AbstractFactoryPattern;
Expand All @@ -24,6 +25,9 @@ internal static class PatternOptions
internal const string AdapterPatternOption = "13";
internal const string FlyweightPatternOption = "14";
internal const string MediatorPatternOption = "15";
internal const string ObserverPatternOption = "16.1";
internal const string ObserverPatternOptionWithType = "16.2";

internal const string ChainOfResponsibilityPatternOption = "21";
internal const string ChainOfResponsibilityPatternOption2 = "21.2";
internal const string ChainOfResponsibilityPatternOption3 = "21.3";
Expand All @@ -33,6 +37,7 @@ internal static class PatternOptions
internal const string StatePatternOptionDriveExample = "23.2";
internal const string StrategyPatternOptionSender = "24";
internal const string StrategyPatternOptionPayment = "24.2";

internal const string FactoryOption = "31";
internal const string AbstractFactoryOption = "32";
internal const string BuilderPatternOption = "33";
Expand Down Expand Up @@ -73,6 +78,14 @@ internal static class PatternOptions
MediatorPatternOption,
new PatternExampleMap("Flyweight Pattern >> Drawing shapes", new MediatorPatternExample())
},
{
ObserverPatternOption,
new PatternExampleMap("Observer Pattern >> News Publisher", new ObserverPatternExample())
},
{
ObserverPatternOptionWithType,
new PatternExampleMap("Observer Pattern >> News Publisher with type", new ObserverPatternExampleWithCategory())
},

// Behavioral Patterns
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GofPatterns.Behavioral.ObserverPattern.Exceptions;

public class NoSubscriptionFoundException : Exception
{
public NoSubscriptionFoundException(string message) : base(message) { }
}
31 changes: 31 additions & 0 deletions GofPatterns/Behavioral/ObserverPattern/Interfaces/IPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace GofPatterns.Behavioral.ObserverPattern.Interfaces;

/// <summary>
/// Publisher (or Broadcaster) interface.
/// Publishers are responsible for managing subscribers and notifying them of events.
/// </summary>
/// <typeparam name="TInput"></typeparam>
public interface IPublisher<TInput>
{
public void AddSubscriber(ISubscriber<TInput> subscriber);

public void RemoveSubscribers();

public void NotifySubscribers(TInput input);
}

/// <summary>
/// Publisher (or Broadcaster) interface.
/// Publishers are responsible for managing subscribers and notifying them of events.
/// Multiple subscribers can be subscribed to a category, and there can be multiple categories.
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TCategory"></typeparam>
public interface IPublisher<TInput, in TCategory> where TCategory : notnull
{
public void AddSubscriber(ISubscriber<TInput> subscriber, TCategory category);

public void RemoveSubscribers(TCategory category);

public void NotifySubscribers(TInput input, TCategory category);
}
11 changes: 11 additions & 0 deletions GofPatterns/Behavioral/ObserverPattern/Interfaces/ISubscriber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace GofPatterns.Behavioral.ObserverPattern.Interfaces;

/// <summary>
/// Subscriber (or Listener) interface.
/// Subscribers are interested in certain events and implement the Update method to handle them.
/// </summary>
/// <typeparam name="TInput"></typeparam>
public interface ISubscriber<in TInput>
{
void Update(TInput input);
}
68 changes: 68 additions & 0 deletions GofPatterns/Behavioral/ObserverPattern/Publisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using GofPatterns.Behavioral.ObserverPattern.Exceptions;
using GofPatterns.Behavioral.ObserverPattern.Interfaces;

namespace GofPatterns.Behavioral.ObserverPattern;

/// <summary>
/// Implementation of the Publisher (or Broadcaster) interface.
/// </summary>
/// <typeparam name="TInput"></typeparam>
public class Publisher<TInput> : IPublisher<TInput>
{
private readonly List<ISubscriber<TInput>> subscribers = new();

public void AddSubscriber(ISubscriber<TInput> subscriber)
{
subscribers.Add(subscriber);
}

public void RemoveSubscribers()
{
subscribers.Clear();
}

public void NotifySubscribers(TInput input)
{
subscribers.ForEach(x => x.Update(input));
}
}

/// <summary>
/// Implementation of the Publisher (or Broadcaster) interface.
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TCategory"></typeparam>
public class Publisher<TInput, TCategory> : IPublisher<TInput, TCategory> where TCategory : notnull
{
private readonly Dictionary<TCategory, IPublisher<TInput>> publishers = new();

public void AddSubscriber(ISubscriber<TInput> subscriber, TCategory category)
{
IPublisher<TInput> publisher;

if (publishers.TryGetValue(category, out var outputPublisher))
publisher = outputPublisher;
else
publishers[category] = publisher = new Publisher<TInput>();

publisher.AddSubscriber(subscriber);
}

public void RemoveSubscribers(TCategory category)
{
VerifySubscription(category);
publishers.Remove(category);
}

public void NotifySubscribers(TInput input, TCategory category)
{
VerifySubscription(category);
publishers[category].NotifySubscribers(input);
}

private void VerifySubscription(TCategory type)
{
if (!publishers.ContainsKey(type))
throw new NoSubscriptionFoundException($"No subscription found for category: {type}");
}
}
Loading