O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.
Writing Maintainable Software
using SOLID Principles
Doug Jones
duggj@yahoo.com
Have you ever played Jenga?
From <http://www.codemag.com/article/1001061>
https://lostechies.com/derickbailey/2009/02/11/solid-development-
principles-in-motivational-pictures/
MVC and Adding the Rails
 MVC popularized by Ruby on Rails
 If you consider a train on rails, the train goes where the r...
What’re we talking about?
 Adding the rails with SOLID Principles and DRY
 Shown using a particular methodology
 Some p...
The DRY Principle
DRY - Don’t Repeat Yourself!
“Every piece of knowledge must have a single, unambiguous, authoritative
re...
Copy/Paste Programming
public UserDetails GetUserDetails(int id)
{
UserDetails userDetails;
string cs = Startup.Configurat...
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack
SOLID principles?
 Principles of Object Oriented Design
 Created by Robert C. Martin (known as Uncle Bob)
 Also co-crea...
The SOLID Principles
as in Robert C. Martin’s Agile Software Development book
 SRP: Single Responsibility Principle
 OCP...
The SOLID Principles
as rearranged by Michael Feathers
 SRP: Single Responsibility Principle
 OCP: Open-Closed Principle...
The SOLID Principles
rearranged by importance
 SRP: Single Responsibility Principle
 DIP: The Dependency Inversion Princ...
SRP: Single Responsibility Principle
A class should have only one reason to change
SOLID
https://lostechies.com/derickbailey/2009/02/11/solid-development-
principles-in-motivational-pictures/ SOLID
public string SendUserEmail(int userId)
{
#region GetUserData
UserContact user = null;
string cs = Startup.Configuration.G...
What could change?
 Could have a SQL Server instance dns change [mitigated]
 SQL table structure could change
 Api url ...
In other words, the following could
change…
 Configuration data – server names, usernames, passwords
 The way we get use...
public string SendUserEmail(int id)
{
var userRepository = new UserSqlRepository();
var user = userRepository.GetUserConta...
After fully refactoring to adhere to SRP
public string SendUserEmail(int userId)
{
var userRepository = new UserSqlReposit...
DIP: Dependency Inversion Principle
(not the DI: Dependency Injection)
 A. High-level modules should not depend on low-le...
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack SOLID
Added the abstraction (interface)
public interface IUserMessageSender
{
void SendUserMessage(UserContact user);
}
------- ...
Inverted the dependencies
Expose dependencies via constructor
public class SmtpUserMessageSender : IUserMessageSender
{
pr...
Chuck Norris, Jon Skeet, and
Immutability (readonly keyword in C#)
 Chuck Norris doesn’t read books…
 Jon Skeet is immut...
…using a config DTO class
public class SmtpMessageSenderConfig
{
public string UserName { get; set; }
public string Passwo...
Or if you want to be hardcore…
public class SmtpUserMessageSenderConfig : ISmtpUserMessageSenderConfig
{
public string Use...
Client usage injecting smtp dependencies
public string SendUserEmail(int userId)
{
var userRepository = new UserSqlReposit...
Inject them all!
public string SendUserEmail(int userId)
{
var userRepository = new UserSqlRepository(new UserSqlRepositor...
Strategy pattern in UserProcessor
public class UserProcessor : IUserProcessor
{
private readonly IUserRepository _userRepo...
The Strategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the...
Principles of reusable object-oriented design
 Program to an interface, not an implementation.
 Favor object composition...
Favor interface inheritance over
implementation inheritance
James Gosling, creator of Java, stated in 2001 interview that ...
Controller acting as Composition Root
public string SendUserEmail(int userId)
{
IUserRepository userRepository = new UserS...
Composition Root?
The main function in an application should have a concrete non-volatile side.
– Uncle Bob
The Compositio...
THE Composition Root
Dependency Injection via Startup.cs file
public void ConfigureServices(IServiceCollection services)
{...
Singletons
SOLID
Singletons
Ensure exactly 1 instance
public sealed class Singleton
{
private static readonly Singleton instance = new Sing...
Avoid Static Cling
 A static member belongs to the type (class) and not to the instance.
 It is a concrete implementatio...
Controller after DI Framework configured
[Route("api/[controller]")]
public class ProcessorController : Controller
{
priva...
Dependency Injection Frameworks
…also known as Inversion of Control (IoC)
 C#
 .NET Core Dependency Injection
 Built in...
DIP, DI, IoC…what’s that?
 Dependency Inversion Principle (DIP)
 A. High-level modules should not depend on low-level mo...
OCP: The Open-Closed Principle
 Software entities (classes, modules, functions, etc…) should be open for
extension, but c...
https://lostechies.com/derickbailey/2009/02/11/solid-development-
principles-in-motivational-pictures/ SOLID
This cannot be extended
public class DIPMessageProcessorController: Controller
{
[HttpGet("sendusermessage/{userId}")]
pub...
New is Glue
 Any time you use the new keyword, you are gluing your code to a particular
implementation. You are permanent...
This can be extended!
public class UserProcessor : IUserProcessor
{
private readonly IUserRepository _userRepository;
priv...
Create another strategy for business rules
public class ExtraditionUserScamEligibility: IUserScamEligibility
{
public bool...
Change strategy for UserMessageSender
public class TextUserMessageSender : IUserMessageSender
{
public void SendUserMessag...
Remember that Strategy Pattern…
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strate...
Just change 2 lines of code…
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddS...
This has been EXTENDED
public class UserProcessor : IUserProcessor
{
private readonly IUserRepository _userRepository;
pri...
Polymorphism
 Changing the underlying implementation of the type to give it different
behavior
 Different types of Polym...
The UPS as a Decorator
Mark Seemann. Dependency Injection in .NET
Extend a strategy with a Decorator
public class CacheDecoratorUserRepository : IUserRepository
{
private readonly IUserRep...
The Decorator Pattern
Attach additional responsibilities to an object dynamically. Decorators provide a
flexible alternati...
.NET Core DI doesn’t directly support Decorator
services.AddSingleton<UserSqlRepository>();
services.AddSingleton<CacheDec...
…you now have new functionality here!
public class UserProcessorNoFactory : IUserProcessorNoFactory
{
private readonly IUs...
Dependency with short lifetime
public class SqlDbConnectionFactory : IDbConnectionFactory
{
private readonly SqlDbConnecti...
Client of DBConnectionFactory
public class UserSqlRepository : IUserRepository
{
private readonly IDbConnectionFactory _db...
New instance every call
using Abstract Factory
public class Sha512HashAlgorithmFactory : IHashAlgorithmFactory
{
public Ha...
Abstract Factory to choose strategy
public class UserMessageSenderFactory : IUserMessageSenderFactory
{
private readonly I...
DI just needed additional registrations…
services.AddSingleton<IUserMessageSender,SmtpUserMessageSender>();
services.AddSi...
Change strategies based on input
public class UserProcessor : IUserProcessor
{
private readonly IUserRepository _userRepos...
Abstract Factory
Provide an interface for creating families of related or dependent objects without
specifying their concr...
Don’t go overboard
 Strategically choose what changes to close design against.
 Resisting premature abstraction is as im...
LSP: Liskov Substitution Principle
 The LSP can be paraphrased as follows:
Subtypes must be substitutable for their base ...
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack SOLID
I have this Serializer
public interface ISerializer
{
T DeserializeObject<T>(string value);
string SerializeObject(object ...
but I want to Serialize Binary
public class BinarySerializer : ISerializer
{
public string SerializeObject(object value)
{...
My LSP violation caused OCP violation!
public class DeserializeMaybe
{
private readonly ISerializer _serializer;
public De...
…and when I try to serialize my User…
another LSP violation
private string SerializeUser(User user)
{
return _serializer.S...
ISP: Interface Segregation Principle
 The ISP:
Clients should not be forced to depend on methods that they do not use.
 ...
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack SOLID
Interface with multiple roles
public interface IMultiRoleUserRepository
{
UserContact GetUserContactById(int userId);
void...
Split the interface to single roles
public interface IUserContactReaderRepository
{
UserContact GetUserContactById(int use...
Repository implementing multiple roles
public class MultiRoleUserSqlRepository : IUserContactReaderRepository,
IUserWriter...
Pure Functions – the ideal
 The function always evaluates the same result value given the same argument
value(s). The fun...
Agile Manifesto
 Individuals and interactions over processes and tools
 Working software over comprehensive documentatio...
A SOLID Manifesto
 Composition over inheritance
 Interface inheritance over implementation inheritance
 Classes with st...
So what’s the downside?
 Class and Interface Explosion
 Complexity (increase in one kind of complexity)
 I have an inst...
…but you now have maintainable
software using SOLID principles!
 Testable!
 What parts should you test?
 What is Michae...
https://lostechies.com/derickbailey/2009/02/11/solid-development-
principles-in-motivational-pictures/
We’ve turned our Je...
…into a well built structure that’s easy
to maintain
Appendix
 The Pragmatic Programmer – Andrew Hunt and David Thomas
 SOLID Jenga reference - http://www.codemag.com/articl...
Writing Maintainable Software
using SOLID Principles
Doug Jones
duggj@yahoo.com
Questions?
Project layout
Próximos SlideShares
Carregando em…5
×

Writing Maintainable Software Using SOLID Principles

743 visualizações

Publicada em

Uncle Bob Martin (Robert C. Martin) conceptualized these 5 object oriented design principles (the SOLID principles) almost 20 years ago, yet they're just as applicable today. I'll walk through the SOLID principles and dependency injection, and offer a methodology that adheres to those principles that's worked well for me. I'll give you examples in .NET Core 2.0, but you'll find these timeless principles work in any language. Come enjoy a discussion away from the technology treadmill!

Publicada em: Tecnologia
  • The final result was amazing, and I highly recommend ⇒ www.HelpWriting.net ⇐ to anyone in the same mindset as me.
       Responder 
    Tem certeza que deseja  Sim  Não
    Insira sua mensagem aqui
  • Don't forget another good way of simplifying your writing is using external resources (such as ⇒ www.WritePaper.info ⇐ ). This will definitely make your life more easier
       Responder 
    Tem certeza que deseja  Sim  Não
    Insira sua mensagem aqui
  • A very good resource can do the writings for HelpWriting.net But use it only if u rly cant write anything down.
       Responder 
    Tem certeza que deseja  Sim  Não
    Insira sua mensagem aqui

Writing Maintainable Software Using SOLID Principles

  1. 1. Writing Maintainable Software using SOLID Principles Doug Jones duggj@yahoo.com
  2. 2. Have you ever played Jenga? From <http://www.codemag.com/article/1001061>
  3. 3. https://lostechies.com/derickbailey/2009/02/11/solid-development- principles-in-motivational-pictures/
  4. 4. MVC and Adding the Rails  MVC popularized by Ruby on Rails  If you consider a train on rails, the train goes where the rails take it. - https://www.ruby-forum.com/topic/135143  by defining a convention and sticking to that you will find that your applications are easy to generate and quick to get up and running. - https://stackoverflow.com/questions/183462/what-does-it-mean-for-a- programming-language-to-be-on-rails  Principles, Patterns, and Practices
  5. 5. What’re we talking about?  Adding the rails with SOLID Principles and DRY  Shown using a particular methodology  Some patterns to help  Dependency Injection  …and how this leads to maintainable software
  6. 6. The DRY Principle DRY - Don’t Repeat Yourself! “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system” As opposed to WET - The Pragmatic Programmer
  7. 7. Copy/Paste Programming public UserDetails GetUserDetails(int id) { UserDetails userDetails; string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value; var connection = new SqlConnection(cs); connection.Open(); try { userDetails = connection.Query<UserDetails>( "select BusinessEntityID,EmailAddress from [Person].[EmailAddress] where BusinessEntityID = @Id", new { Id = id }).FirstOrDefault(); } finally { connection.Dispose(); } return userDetails; } public UserContact GetUserContact(int id) { UserContact user = null; string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value; var connection = new SqlConnection(cs); connection.Open(); try { user = connection.Query<UserContact>( @"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressID FROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityID INNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityID where ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault(); } finally { connection.Dispose(); } return user; }
  8. 8. store.deviq.com/products/software-craftsmanship- calendars-2017-digital-image-pack
  9. 9. SOLID principles?  Principles of Object Oriented Design  Created by Robert C. Martin (known as Uncle Bob)  Also co-creator of the Agile Manifesto  Series of articles for The C++ Report (1996)  In his book Agile Software Development Principles, Patterns, and Practices
  10. 10. The SOLID Principles as in Robert C. Martin’s Agile Software Development book  SRP: Single Responsibility Principle  OCP: Open-Closed Principle  LSP: The Liskov Substitution Principle  DIP: The Dependency Inversion Principle  ISP: The Interface-Segregation Principle
  11. 11. The SOLID Principles as rearranged by Michael Feathers  SRP: Single Responsibility Principle  OCP: Open-Closed Principle  LSP: The Liskov Substitution Principle  ISP: The Interface-Segregation Principle  DIP: The Dependency Inversion Principle
  12. 12. The SOLID Principles rearranged by importance  SRP: Single Responsibility Principle  DIP: The Dependency Inversion Principle  OCP: Open-Closed Principle  LSP: The Liskov Substitution Principle  ISP: The Interface-Segregation Principle - per Uncle Bob on Hanselminutes podcast …and they’re about Managing Dependencies!
  13. 13. SRP: Single Responsibility Principle A class should have only one reason to change SOLID
  14. 14. https://lostechies.com/derickbailey/2009/02/11/solid-development- principles-in-motivational-pictures/ SOLID
  15. 15. public string SendUserEmail(int userId) { #region GetUserData UserContact user = null; string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value; var connection = new SqlConnection(cs); connection.Open(); try { user = connection.Query<UserContact>( @"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressID FROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityID INNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityID where ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault(); } finally { connection.Dispose(); } #endregion #region GetAdditionalInfoOnUser var httpClient = new HttpClient(); string userCoordinatesJson = httpClient.GetStringAsync( $"http://localhost:62032/api/geolocator/locateaddressid/{user.AddressId}").Result; var userCoordinates = JsonConvert.DeserializeObject<GeocodeCoordinates>(userCoordinatesJson); #endregion #region Business Logic if (IsNearby(userCoordinates)) { return "User is nearby. Do not send the scam email!"; } #endregion #region BuildAndSendUserEmail SmtpClient client = new SmtpClient(Startup.Configuration.GetSection("Smtp:Host").Value); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential { UserName = Startup.Configuration.GetSection("Smtp:UserName").Value, Password = Startup.Configuration.GetSection("Smtp:Password").Value }; MailMessage mailMessage = new MailMessage { From = new MailAddress( Startup.Configuration.GetSection("Smtp:FromAddress").Value), Subject = $"Congratulations, {user.FirstName}, you just won!", Body = "You won 1.3 million dollars! " + "There are just some taxes you need to pay on those winnings first. " + "Please send payment to scam@paypal.com.", }; mailMessage.To.Add(user.EmailAddress); client.Send(mailMessage); #endregion return "Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  16. 16. What could change?  Could have a SQL Server instance dns change [mitigated]  SQL table structure could change  Api url could change (and actually WILL, since pointing to localhost)  Api interface could change  Business rules could change  Now only send to users in countries with weak extradition laws  Email SMTP server could change, from address, user/pass [mitigated]  Subject and body of email could change SOLID
  17. 17. In other words, the following could change…  Configuration data – server names, usernames, passwords  The way we get user data  The way we get user coordinates  The way we determine user eligibility  The way we contact users SOLID
  18. 18. public string SendUserEmail(int id) { var userRepository = new UserSqlRepository(); var user = userRepository.GetUserContactById(id); #region GetAdditionalInfoOnUser var httpClient = new HttpClient(); string userCoordinatesJson = httpClient.GetStringAsync( $"http://localhost:62032/api/geolocator/locateaddressid/{user.AddressId}").Result; … Refactor to UserRepository public class UserSqlRepository { private string _connectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value; public UserContact GetUserContactById(int userId) { UserContact user; using (var connection = GetOpenConnection()) { user = connection.Query<UserContact>( @"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressID FROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityID INNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityID where ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault(); } return user; } SOLID
  19. 19. After fully refactoring to adhere to SRP public string SendUserEmail(int userId) { var userRepository = new UserSqlRepository(); UserContact user = userRepository.GetUserContactById(userId); var userServiceClient = new UserServiceClient(); GeocodeCoordinates userCoordinates = userServiceClient.GetUserCoordinates(user.AddressId); var userScamEligibility = new NearbyUserScamEligibility(); if (userScamEligibility.IsUserScamEligible(user, userCoordinates)) { return "Too risky. User not eligible for our scam."; } var messageSender = new SmtpUserMessageSender(); messageSender.SendUserMessage(user); return "Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  20. 20. DIP: Dependency Inversion Principle (not the DI: Dependency Injection)  A. High-level modules should not depend on low-level modules. Both should depend on abstractions.  B. Abstractions should not depend on details. Details should depend on abstractions. - Uncle Bob  Separate implementations from abstractions  Forces us to code to abstractions/interfaces  Separate construction from use  Allow for much easier testing  Allow for much easier changes and therefore maintainability SOLID
  21. 21. store.deviq.com/products/software-craftsmanship- calendars-2017-digital-image-pack SOLID
  22. 22. Added the abstraction (interface) public interface IUserMessageSender { void SendUserMessage(UserContact user); } ------- SEPARATE FILE, ideally separate dll/package ------- public class SmtpUserMessageSender : IUserMessageSender { private readonly string _smtpUserName = Startup.Configuration.GetSection("Smtp:UserName").Value; private readonly string _smtpPassword = Startup.Configuration.GetSection("Smtp:Password").Value; private readonly string _smtpFromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value; private readonly string _smtpHost = Startup.Configuration.GetSection("Smtp:Host").Value; public void SendUserMessage(UserContact user) { SmtpClient client = new SmtpClient(_smtpHost); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential { UserName = _smtpUserName, Password = _smtpPassword }; MailMessage mailMessage = new MailMessage { From = new MailAddress(_smtpFromAddress), Subject = $"Congratulations, {user.FirstName}, you just won!", Body = "You won 1.3 million dollars! " + "There are just some taxes you need to pay on those winnings first. " + "Please send payment to scam@paypal.com.", }; mailMessage.To.Add(user.EmailAddress); client.Send(mailMessage); } SOLID
  23. 23. Inverted the dependencies Expose dependencies via constructor public class SmtpUserMessageSender : IUserMessageSender { private readonly ISmtpUserMessageSenderConfig _config; public SmtpUserMessageSender(ISmtpUserMessageSenderConfig config) { _config = config; } public void SendUserMessage(UserContact user) { SmtpClient client = new SmtpClient(_config.Host); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential { UserName = _config.UserName, Password = _config.Password }; MailMessage mailMessage = new MailMessage { From = new MailAddress(_config.FromAddress), Subject = $"Congratulations, {user.FirstName}, you just won!", Body = "You won 1.3 million dollars! " + "There are just some taxes you need to pay on those winnings first. " + "Please send payment to scam@paypal.com.", }; mailMessage.To.Add(user.EmailAddress); client.Send(mailMessage); } SOLID
  24. 24. Chuck Norris, Jon Skeet, and Immutability (readonly keyword in C#)  Chuck Norris doesn’t read books…  Jon Skeet is immutable… SOLID
  25. 25. …using a config DTO class public class SmtpMessageSenderConfig { public string UserName { get; set; } public string Password { get; set; } public string Host { get; set; } public string FromAddress { get; set; } } SOLID
  26. 26. Or if you want to be hardcore… public class SmtpUserMessageSenderConfig : ISmtpUserMessageSenderConfig { public string UserName { get; set; } public string Password { get; set; } public string Host { get; set; } public string FromAddress { get; set; } } public interface ISmtpUserMessageSenderConfig { string UserName { get; } string Password { get; } string Host { get; } string FromAddress { get; } } SOLID
  27. 27. Client usage injecting smtp dependencies public string SendUserEmail(int userId) { var userRepository = new UserSqlRepository(); UserContact user = userRepository.GetUserContactById(userId); var userServiceClient = new UserServiceClient(); GeocodeCoordinates userCoordinates = userServiceClient.GetUserCoordinates(user.AddressId); var userScamEligibility = new NearbyUserScamEligibility(); if (userScamEligibility.IsUserScamEligible(user, userCoordinates)) { return "Too risky. User not eligible for our scam."; } IUserMessageSender userMessageSender = new SmtpUserMessageSender( new SmtpUserMessageSenderConfig { Host = Startup.Configuration.GetSection("Smtp:Host").Value, UserName = Startup.Configuration.GetSection("Smtp:UserName").Value, Password = Startup.Configuration.GetSection("Smtp:Password").Value, FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value }); userMessageSender.SendUserMessage(user); return "Scam Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  28. 28. Inject them all! public string SendUserEmail(int userId) { var userRepository = new UserSqlRepository(new UserSqlRepositoryConfig { ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value }); UserContact user = userRepository.GetUserContactById(userId); var userServiceClient = new UserServiceClient(new UserServiceClientConfig { UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value },new HttpClientHandler()); GeocodeCoordinates userCoordinates = userServiceClient.GetUserCoordinates(user.AddressId); var userScamEligibility = new NearbyUserScamEligibility(); if (userScamEligibility.IsUserScamEligible(user, userCoordinates)) { return "Too risky. User not eligible for our scam."; } IUserMessageSender userMessageSender = new SmtpUserMessageSender( new SmtpUserMessageSenderConfig { Host = Startup.Configuration.GetSection("Smtp:Host").Value, UserName = Startup.Configuration.GetSection("Smtp:UserName").Value, Password = Startup.Configuration.GetSection("Smtp:Password").Value, FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value }); userMessageSender.SendUserMessage(user); return "Scam Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  29. 29. Strategy pattern in UserProcessor public class UserProcessor : IUserProcessor { private readonly IUserRepository _userRepository; private readonly IUserScamEligibility _userScamEligibility; private readonly IUserMessageSender _userMessageSender; public UserProcessor(IUserRepository userRepository, IUserScamEligibility userScamEligibility, IUserMessageSender userMessageSender) { this._userRepository = userRepository; this._userScamEligibility = userScamEligibility; _userMessageSender = userMessageSender; } public bool SendUserMessageByUserId(int userId) { var user = _userRepository.GetUserContactById(userId); if (!_userScamEligibility.IsUserScamEligible(user)) { return false; } _userMessageSender.SendUserMessage(user); return true; } } SOLID
  30. 30. The Strategy Pattern Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. – GOF This is achieved via composition and not via inheritance. SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw
  31. 31. Principles of reusable object-oriented design  Program to an interface, not an implementation.  Favor object composition over class inheritance. Object composition has another effect on system design. Favoring object composition over class inheritance helps you keep each class encapsulated and focused on one task. Your classes and class hierarchies will remain small and will be less likely to grow into unmanageable monsters… …our experience is that designers overuse inheritance as a reuse technique, and designs are often made more reusable (and simpler) by depending more on object composition. GOF - Design Patterns: Elements of Reusable Object-Oriented Software (pp. 19-20). Pearson Education (1995). SOLID
  32. 32. Favor interface inheritance over implementation inheritance James Gosling, creator of Java, stated in 2001 interview that he was wrestling with the idea of removing class inheritance: “Rather than subclassing, just use pure interfaces. It's not so much that class inheritance is particularly bad. It just has problems.” https://www.javaworld.com/article/2073649/core-java/why-extends-is- evil.html [James Gosling on what he'd change in Java] explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible. - Allen Holub 2003 http://www.artima.com/intv/gosling34.html SOLID
  33. 33. Controller acting as Composition Root public string SendUserEmail(int userId) { IUserRepository userRepository = new UserSqlRepository(new UserSqlRepositoryConfig { ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value }); IUserServiceClient userServiceClient = new UserServiceClient(new UserServiceClientConfig { UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value },new HttpClientHandler()); IUserScamEligibility userScamEligibility = new NearbyUserScamEligibility(userServiceClient); IUserMessageSender userMessageSender = new SmtpUserMessageSender( new SmtpUserMessageSenderConfig { Host = Startup.Configuration.GetSection("Smtp:Host").Value, UserName = Startup.Configuration.GetSection("Smtp:UserName").Value, Password = Startup.Configuration.GetSection("Smtp:Password").Value, FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value }); IUserProcessorNoFactory userProcessor = new UserProcessorNoFactory(userRepository,userScamEligibility,userMessageSender); userProcessor.SendUserMessageByUserId(userId); return "Scam Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  34. 34. Composition Root? The main function in an application should have a concrete non-volatile side. – Uncle Bob The Composition Root is the place where we create and compose the objects resulting in an object-graph that constitutes the application. This place should be as close as possible to the entry point of the application. From <http://www.dotnetcurry.com/patterns-practices/1285/clean-composition-roots-dependency-injection> SOLID
  35. 35. THE Composition Root Dependency Injection via Startup.cs file public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IUserProcessor, UserProcessor>(); services.AddSingleton<IUserRepository, UserRepository>(); services.AddSingleton<IUserScamEligibility, UserScamEligibility>(); services.AddSingleton<IUserServiceClient, UserServiceClient>(); services.AddSingleton<IUserMessageSender, SmtpUserMessageSender>(); services.AddSingleton<UserRepositoryConfig>(provider => new UserRepositoryConfig { ConnectionString = _configuration.GetSection("ConnectionStrings:DefaultConnection").Value }); services.AddSingleton<UserServiceClientConfig>(provider => new UserServiceClientConfig { UserDetailsUrl = _configuration.GetSection("UserApi:UserDetailsUrl").Value }); services.AddSingleton<SmtpUserMessageSenderConfig>(provider => new SmtpUserMessageSenderConfig { Host = _configuration.GetSection("Smtp:Host").Value, UserName = _configuration.GetSection("Smtp:UserName").Value, Password = _configuration.GetSection("Smtp:Password").Value, FromAddress = _configuration.GetSection("Smtp:FromAddress").Value }); } SOLID
  36. 36. Singletons SOLID
  37. 37. Singletons Ensure exactly 1 instance public sealed class Singleton { private static readonly Singleton instance = new Singleton(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return instance; } } } Jon Skeet - http://csharpindepth.com/articles/general/singleton.aspx SOLID
  38. 38. Avoid Static Cling  A static member belongs to the type (class) and not to the instance.  It is a concrete implementation only and we cannot reference via abstraction.  Static Cling is a code smell used to describe the undesirable coupling introduced by accessing static (global) functionality, either as variables or methods. This coupling can make it difficult to test or modify the behavior of software systems. - http://deviq.com/static-cling/  I tend to care when… I can’t unit test!  static includes external dependency.  MyNamespace.MyRepo.InsertInDb(…)  File.ReadAllText above  Static method includes non-deterministic behavior  DateTime.UtcNow(); System.IO.File.ReadAllText("SomeFile.txt"); SOLID
  39. 39. Controller after DI Framework configured [Route("api/[controller]")] public class ProcessorController : Controller { private readonly IUserProcessor _userProcessor; public ProcessorController(IUserProcessor userProcessor) { _userProcessor = userProcessor; } // GET api/ProcessorController/sendusermessage/5 [HttpGet("sendusermessage/{userId}")] public string SendUserMessage(int userId) { try { if (_userProcessor.SendUserMessageByUserId(userId)) { return "Scam Message Sent! Just wait for the riches to come pouring in..."; } return "Too risky. User not eligible for our scam."; } catch(Exception ex) { return "Error occurred sending message. Exception message: " + ex.Message; } } SOLID
  40. 40. Dependency Injection Frameworks …also known as Inversion of Control (IoC)  C#  .NET Core Dependency Injection  Built into ASP.NET Core  A quick NuGet package away otherwise for NetStandard 2.0  Microsoft.Extensions.DependencyInjection  SimpleInjector  My preference for anything complicated or on .NET Framework  Highly opinionated  Ninject  Complex DI made easy  Not opinionated: will help you get it done regardless of pattern you choose  Java  Spring SOLID
  41. 41. DIP, DI, IoC…what’s that?  Dependency Inversion Principle (DIP)  A. High-level modules should not depend on low-level modules. Both should depend on abstractions.  B. Abstractions should not depend on details. Details should depend on abstractions.  Dependency Injection (DI)  Inversion of Control (IoC)  Hollywood Principle – “Don't call us, we'll call you”  Template Method  Events/Observable  IoC Container and DI Framework  Same thing! SOLIDhttps://martinfowler.com/bliki/InversionOfControl.html
  42. 42. OCP: The Open-Closed Principle  Software entities (classes, modules, functions, etc…) should be open for extension, but closed for modification  We can extend client classes that use interfaces, but will need to change code. SOLID
  43. 43. https://lostechies.com/derickbailey/2009/02/11/solid-development- principles-in-motivational-pictures/ SOLID
  44. 44. This cannot be extended public class DIPMessageProcessorController: Controller { [HttpGet("sendusermessage/{userId}")] public string SendUserEmail(int userId) { IUserRepository userRepository = new UserSqlRepository(new UserSqlRepositoryConfig { ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value }); IUserServiceClient userServiceClient = new UserServiceClient(new UserServiceClientConfig { UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value },new HttpClientHandler()); IUserScamEligibility userScamEligibility = new NearbyUserScamEligibility(userServiceClient); IUserMessageSender userMessageSender = new SmtpUserMessageSender( new SmtpUserMessageSenderConfig { Host = Startup.Configuration.GetSection("Smtp:Host").Value, UserName = Startup.Configuration.GetSection("Smtp:UserName").Value, Password = Startup.Configuration.GetSection("Smtp:Password").Value, FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value }); IUserProcessorNoFactory userProcessor = new UserProcessorNoFactory(userRepository,userScamEligibility,userMessageSender); userProcessor.SendUserMessageByUserId(userId); return "Scam Email Sent! Just wait for the riches to come pouring in..."; } SOLID
  45. 45. New is Glue  Any time you use the new keyword, you are gluing your code to a particular implementation. You are permanently (short of editing, recompiling, and redeploying) hard-coding your application to work with a particular class’s implementation. - Steve Smith, https://ardalis.com/new-is-glue SOLID
  46. 46. This can be extended! public class UserProcessor : IUserProcessor { private readonly IUserRepository _userRepository; private readonly IUserScamEligibility _userScamEligibility; private readonly IUserMessageSender _userMessageSender; public UserProcessor(IUserRepository userRepository, IUserScamEligibility userScamEligibility, IUserMessageSender userMessageSender) { this._userRepository = userRepository; this._userScamEligibility = userScamEligibility; _userMessageSender = userMessageSender; } public bool SendUserMessageByUserId(int userId) { var user = _userRepository.GetUserContactById(userId); if (!_userScamEligibility.IsUserScamEligible(user)) { return false; } _userMessageSender.SendUserMessage(user); return true; } } SOLID
  47. 47. Create another strategy for business rules public class ExtraditionUserScamEligibility: IUserScamEligibility { public bool IsUserScamEligible(UserContact user) { return !CountryLikelyToSeekExtradition(user.Country); } SOLID
  48. 48. Change strategy for UserMessageSender public class TextUserMessageSender : IUserMessageSender { public void SendUserMessage(UserContact user) { if (!IsMobilePhone(user.PhoneNumber)) { throw new SolidException("Cannot send text to non-mobile phone"); } SendTextMessage(user.PhoneNumber); } SOLID
  49. 49. Remember that Strategy Pattern… Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. – GOF This is achieved via composition and not via inheritance. SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw
  50. 50. Just change 2 lines of code… public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IUserScamEligibility, ExtraditionUserScamEligibility>(); services.AddSingleton<IUserMessageSender, TextUserMessageSender>(); … SOLID
  51. 51. This has been EXTENDED public class UserProcessor : IUserProcessor { private readonly IUserRepository _userRepository; private readonly IUserScamEligibility _userScamEligibility; private readonly IUserMessageSender _userMessageSender; public UserProcessor(IUserRepository userRepository, IUserScamEligibility userScamEligibility, IUserMessageSender userMessageSender) { this._userRepository = userRepository; this._userScamEligibility = userScamEligibility; _userMessageSender = userMessageSender; } public bool SendUserMessageByUserId(int userId) { var user = _userRepository.GetUserContactById(userId); if (!_userScamEligibility.IsUserScamEligible(user)) { return false; } _userMessageSender.SendUserMessage(user); return true; } } SOLID
  52. 52. Polymorphism  Changing the underlying implementation of the type to give it different behavior  Different types of Polymorphism  Subtype Polymorphism (inheritance based), which can be from a supertype that is  Abstract class  Concrete class  Interface  Duck Typing  Dynamic languages like JavaScript and Ruby  If it has the Quack() function, I can call it regardless of the type  Parametric Polymorphism  Generics with parameter polymorphism  List<T> open type with closed type as List<string>  Ad hoc Polymorphism  Method overloading SOLID
  53. 53. The UPS as a Decorator Mark Seemann. Dependency Injection in .NET
  54. 54. Extend a strategy with a Decorator public class CacheDecoratorUserRepository : IUserRepository { private readonly IUserRepository _userRepository; private readonly TimeSpan _timeToLive; private readonly ConnectionMultiplexer _redis; public CacheDecoratorUserRepository(IUserRepository userRepository, CacheDecoratorUserRepositoryConfig config) { _userRepository = userRepository; _timeToLive = config.TimeToLive; _redis = ConnectionMultiplexer.Connect(config.ConfigurationString); } public UserContact GetUserContactById(int userId) { //this would have multiple try/catch blocks IDatabase db = _redis.GetDatabase(); string key = $"SolidCore:GetUserContactById:{userId}"; string redisValue = db.StringGet(key); if (redisValue != null) { return JsonConvert.DeserializeObject<UserContact>(redisValue); } var user = _userRepository.GetUserContactById(userId); if (user == null) { return null; } string serializedValue = JsonConvert.SerializeObject(user); db.StringSet(key, serializedValue,_timeToLive); return user; } } SOLID
  55. 55. The Decorator Pattern Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. - GoF SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw
  56. 56. .NET Core DI doesn’t directly support Decorator services.AddSingleton<UserSqlRepository>(); services.AddSingleton<CacheDecoratorUserRepositoryConfig>(provider => new CacheDecoratorUserRepositoryConfig { ConfigurationString = _configuration.GetSection("Redis:ConfigurationString").Value, TimeToLive = TimeSpan.Parse(_configuration.GetSection("Redis:TimeToLive").Value) }); services.AddSingleton<IUserRepository>(provider => { var userRepository = provider.GetService<UserSqlRepository>(); var config = provider.GetService<CacheDecoratorUserRepositoryConfig>(); return new CacheDecoratorUserRepository(userRepository, config); }); SOLID
  57. 57. …you now have new functionality here! public class UserProcessorNoFactory : IUserProcessorNoFactory { private readonly IUserRepository _userRepository; private readonly IUserScamEligibility _userScamEligibility; private readonly IUserMessageSender _userMessageSender; public UserProcessorNoFactory(IUserRepository userRepository, IUserScamEligibility userScamEligibility, IUserMessageSender userMessageSender) { this._userRepository = userRepository; this._userScamEligibility = userScamEligibility; _userMessageSender = userMessageSender; } public bool SendUserMessageByUserId(int userId) { var user = _userRepository.GetUserContactById(userId); if (!_userScamEligibility.IsUserScamEligible(user)) { return false; } _userMessageSender.SendUserMessage(user); return true; } } SOLID
  58. 58. Dependency with short lifetime public class SqlDbConnectionFactory : IDbConnectionFactory { private readonly SqlDbConnectionFactoryConfig _config; public SqlDbConnectionFactory(SqlDbConnectionFactoryConfig config) { _config = config; } public IDbConnection GetOpenConnection() { var connection = new SqlConnection(_config.ConnectionString); connection.Open(); return connection; } } SOLID
  59. 59. Client of DBConnectionFactory public class UserSqlRepository : IUserRepository { private readonly IDbConnectionFactory _dbConnectionFactory; public UserSqlRepository(IDbConnectionFactory dbConnectionFactory) { _dbConnectionFactory = dbConnectionFactory; } public UserContact GetUserContactById(int userId) { UserContact userContact; using (var connection = _dbConnectionFactory.GetOpenConnection()) { userContact = connection.Query<UserContact>( @"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressID FROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityID INNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityID where ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault(); } return userContact; }
  60. 60. New instance every call using Abstract Factory public class Sha512HashAlgorithmFactory : IHashAlgorithmFactory { public HashAlgorithm GetInstance() { return System.Security.Cryptography.SHA512.Create(); } } SOLID
  61. 61. Abstract Factory to choose strategy public class UserMessageSenderFactory : IUserMessageSenderFactory { private readonly IEnumerable<IUserMessageSender> _userMessageSenders; public UserMessageSenderFactory(IEnumerable<IUserMessageSender> userMessageSenders) { _userMessageSenders = userMessageSenders; } public IUserMessageSender GetInstance(string messageType) { var userMessageSender = _userMessageSenders.FirstOrDefault(x => x.MessageType.Equals(messageType, StringComparison.OrdinalIgnoreCase)); if (userMessageSender == null) { throw new SolidException("Invalid Message Type"); } return userMessageSender; } } SOLID
  62. 62. DI just needed additional registrations… services.AddSingleton<IUserMessageSender,SmtpUserMessageSender>(); services.AddSingleton<IUserMessageSender, TextUserMessageSender>(); services.AddSingleton<IUserMessageSenderFactory, UserMessageSenderFactory>(); SOLID
  63. 63. Change strategies based on input public class UserProcessor : IUserProcessor { private readonly IUserRepository _userRepository; private readonly IUserScamEligibility _userScamEligibility; private readonly IUserMessageSenderFactory _userMessageSenderFactory; public UserProcessor(IUserRepository userRepository, IUserScamEligibility userScamEligibility, IUserMessageSenderFactory userMessageSenderFactory) { this._userRepository = userRepository; this._userScamEligibility = userScamEligibility; this._userMessageSenderFactory = userMessageSenderFactory; } public bool SendUserMessageByUserId(int userId, string messageType) { var userMessageSender = _userMessageSenderFactory.GetInstance(messageType); var user = _userRepository.GetUserContactById(userId); if (!_userScamEligibility.IsUserScamEligible(user)) { return false; } userMessageSender.SendUserMessage(user); return true; } } SOLID
  64. 64. Abstract Factory Provide an interface for creating families of related or dependent objects without specifying their concrete classes. – GoF It offers a good alternative to the complete transfer of control that’s involved in full INVERSION OF CONTROL, because it partially allows the consumer to control the lifetime of the DEPENDENCIES created by the factory; the factory still controls what is being created and how creation happens. - Mark Seemann. Dependency Injection in .NET (Kindle Locations 3628-3630). Manning Publications. SOLID
  65. 65. Don’t go overboard  Strategically choose what changes to close design against.  Resisting premature abstraction is as important as abstraction itself.  Over-conformance to the principles leads to the design smell of Needless Complexity.  No significant program can be 100% closed. - Uncle Bob, taken from books, articles, podcasts I had a problem, so I tried to solve it with Java… SOLID
  66. 66. LSP: Liskov Substitution Principle  The LSP can be paraphrased as follows: Subtypes must be substitutable for their base types  Can’t add unexpected exceptions  Contravariance of method arguments in the subtype  Covariance of return types in the subtype  Preconditions cannot be strengthened in a subtype  Postconditions cannot be weakened in a subtype.  Invariants of the supertype must be preserved in a subtype.  History constraint  …which we’ll use to also mean implementations of interfaces must be substitutable for other implementations SOLID
  67. 67. store.deviq.com/products/software-craftsmanship- calendars-2017-digital-image-pack SOLID
  68. 68. I have this Serializer public interface ISerializer { T DeserializeObject<T>(string value); string SerializeObject(object value); } public class JsonSerializer : ISerializer { public T DeserializeObject<T>(string value) { return JsonConvert.DeserializeObject<T>(value); } public string SerializeObject(object value) { return JsonConvert.SerializeObject(value); } } SOLID
  69. 69. but I want to Serialize Binary public class BinarySerializer : ISerializer { public string SerializeObject(object value) { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, value); stream.Flush(); stream.Position = 0; return Convert.ToBase64String(stream.ToArray()); } } public T DeserializeObject<T>(string value) { throw new NotImplementedException(); } } SOLID
  70. 70. My LSP violation caused OCP violation! public class DeserializeMaybe { private readonly ISerializer _serializer; public DeserializeMaybe(ISerializer serializer) { _serializer = serializer; } public User.User DeserializeUser(string serializedUser) { if(_serializer is JsonSerializer) { return _serializer.DeserializeObject<User.User>(serializedUser); } return null; } } SOLID
  71. 71. …and when I try to serialize my User… another LSP violation private string SerializeUser(User user) { return _serializer.SerializeObject(user); } SOLID
  72. 72. ISP: Interface Segregation Principle  The ISP: Clients should not be forced to depend on methods that they do not use.  Clients should not know about objects as a single class with a noncohesive interface. SOLID
  73. 73. store.deviq.com/products/software-craftsmanship- calendars-2017-digital-image-pack SOLID
  74. 74. Interface with multiple roles public interface IMultiRoleUserRepository { UserContact GetUserContactById(int userId); void InsertUser(User user); void UpdateEmailAddress(int userId, string emailAddress); } SOLID
  75. 75. Split the interface to single roles public interface IUserContactReaderRepository { UserContact GetUserContactById(int userId); } public interface IUserWriterRepository { void InsertUser(User user); void UpdateEmailAddress(int userId, string emailAddress); } SOLID
  76. 76. Repository implementing multiple roles public class MultiRoleUserSqlRepository : IUserContactReaderRepository, IUserWriterRepository { private readonly IDbConnectionFactory _dbConnectionFactory; public MultiRoleUserSqlRepository(IDbConnectionFactory dbConnectionFactory) { _dbConnectionFactory = dbConnectionFactory; } public UserContact GetUserContactById(int userId) { UserContact userContact; using (var connection = _dbConnectionFactory.GetOpenConnection()) { userContact = ... } return userContact; } public void InsertUser(User user) { ... } public void UpdateEmailAddress(int userId, string emailAddress) { ... } SOLID
  77. 77. Pure Functions – the ideal  The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (usually—see below).  Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices (usually—see below). https://en.wikipedia.org/wiki/Pure_function
  78. 78. Agile Manifesto  Individuals and interactions over processes and tools  Working software over comprehensive documentation  Customer collaboration over contract negotiation  Responding to change over following a plan That is, while there is value in the items on the right, we value the items on the left more.
  79. 79. A SOLID Manifesto  Composition over inheritance  Interface inheritance over implementation inheritance  Classes with state OR behavior over classes with state AND behavior  Singletons over multiple instances or statics  Volatile state scoped to functions over volatile state scoped to classes That is, while there is value in the items on the right, I value the items on the left more.
  80. 80. So what’s the downside?  Class and Interface Explosion  Complexity (increase in one kind of complexity)  I have an instance of the IService interface. What implementation am I using?  Constructor injection spreads like a virus
  81. 81. …but you now have maintainable software using SOLID principles!  Testable!  What parts should you test?  What is Michael Feather’s definition of legacy code?  Easy to change  Particularly at the abstractions (the “seams” - Seemann)  Code can be reused  Design easy to preserve  Forces cognizance of dependencies  Requires much smaller mental model  Small functions with method injection isolated from outside world requires MUCH SMALLER mental model of code. Complexity greatly reduced. Instead of having to understand system, you can just understand THAT function. - Mark Seemann on .NET Rocks podcast
  82. 82. https://lostechies.com/derickbailey/2009/02/11/solid-development- principles-in-motivational-pictures/ We’ve turned our Jenga game…
  83. 83. …into a well built structure that’s easy to maintain
  84. 84. Appendix  The Pragmatic Programmer – Andrew Hunt and David Thomas  SOLID Jenga reference - http://www.codemag.com/article/1001061  Images noted from Steve Smith’s Software Craftsmanship Calendars - store.deviq.com/products/software- craftsmanship-calendars-2017-digital-image-pack – used with express written permission  Composition root definition - http://www.dotnetcurry.com/patterns-practices/1285/clean-composition-roots- dependency-injection  SOLID Motivational Pictures from Los Techies - https://lostechies.com/derickbailey/2009/02/11/solid-development- principles-in-motivational-pictures/  Microsoft .NET Application Architecture - Architecting Modern Web Applications with ASP.NET Core and Azure  Mark Seemann. Dependency Injection in .NET  Feathers, Michael - Working Effectively With Legacy Code  UML Diagrams - https://yuml.me/diagram/scruffy/class/draw  IoC Definition - https://martinfowler.com/bliki/InversionOfControl.html  Pure Function - https://en.wikipedia.org/wiki/Pure_function  IoC Explained - https://martinfowler.com/bliki/InversionOfControl.html  New is Glue - https://ardalis.com/new-is-glue  Static Cling - http://deviq.com/static-cling/  6 ways to make a singleton - http://csharpindepth.com/articles/general/singleton.aspx  GoF - Design Patterns: Elements of Reusable Object-Oriented Software
  85. 85. Writing Maintainable Software using SOLID Principles Doug Jones duggj@yahoo.com Questions?
  86. 86. Project layout

×