SlideShare a Scribd company logo
1 of 86
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 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
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
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
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;
}
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-creator of the Agile Manifesto
 Series of articles for The C++ Report (1996)
 In his book Agile Software Development Principles, Patterns, and Practices
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
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
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!
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.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
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
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
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
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
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
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack SOLID
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
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
Chuck Norris, Jon Skeet, and
Immutability (readonly keyword in C#)
 Chuck Norris doesn’t read books…
 Jon Skeet is immutable…
SOLID
…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
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
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
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
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
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
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
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
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
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
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
Singletons
SOLID
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
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
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
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
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
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
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}")]
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
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
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
Create another strategy for business rules
public class ExtraditionUserScamEligibility: IUserScamEligibility
{
public bool IsUserScamEligible(UserContact user)
{
return !CountryLikelyToSeekExtradition(user.Country);
}
SOLID
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
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
Just change 2 lines of code…
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IUserScamEligibility, ExtraditionUserScamEligibility>();
services.AddSingleton<IUserMessageSender, TextUserMessageSender>();
…
SOLID
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
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
The UPS as a Decorator
Mark Seemann. Dependency Injection in .NET
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
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
.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
…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
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
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;
}
New instance every call
using Abstract Factory
public class Sha512HashAlgorithmFactory : IHashAlgorithmFactory
{
public HashAlgorithm GetInstance()
{
return System.Security.Cryptography.SHA512.Create();
}
}
SOLID
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
DI just needed additional registrations…
services.AddSingleton<IUserMessageSender,SmtpUserMessageSender>();
services.AddSingleton<IUserMessageSender, TextUserMessageSender>();
services.AddSingleton<IUserMessageSenderFactory, UserMessageSenderFactory>();
SOLID
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
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
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
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
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 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
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
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
…and when I try to serialize my User…
another LSP violation
private string SerializeUser(User user)
{
return _serializer.SerializeObject(user);
}
SOLID
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
store.deviq.com/products/software-craftsmanship-
calendars-2017-digital-image-pack SOLID
Interface with multiple roles
public interface IMultiRoleUserRepository
{
UserContact GetUserContactById(int userId);
void InsertUser(User user);
void UpdateEmailAddress(int userId, string emailAddress);
}
SOLID
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
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
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
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.
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.
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
…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
https://lostechies.com/derickbailey/2009/02/11/solid-development-
principles-in-motivational-pictures/
We’ve turned our Jenga game…
…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/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
Writing Maintainable Software
using SOLID Principles
Doug Jones
duggj@yahoo.com
Questions?
Project layout

More Related Content

What's hot

Solid principles of oo design
Solid principles of oo designSolid principles of oo design
Solid principles of oo designConfiz
 
Implementing The Open/Closed Principle
Implementing The Open/Closed PrincipleImplementing The Open/Closed Principle
Implementing The Open/Closed PrincipleSam Hennessy
 
Refactoring Applications using SOLID Principles
Refactoring Applications using SOLID PrinciplesRefactoring Applications using SOLID Principles
Refactoring Applications using SOLID PrinciplesSteven Smith
 
"SOLID" Object Oriented Design Principles
"SOLID" Object Oriented Design Principles"SOLID" Object Oriented Design Principles
"SOLID" Object Oriented Design PrinciplesSerhiy Oplakanets
 
Refactoring to SOLID Code
Refactoring to SOLID CodeRefactoring to SOLID Code
Refactoring to SOLID CodeAdil Mughal
 
S.O.L.I.D. Principles for Software Architects
S.O.L.I.D. Principles for Software ArchitectsS.O.L.I.D. Principles for Software Architects
S.O.L.I.D. Principles for Software ArchitectsRicardo Wilkins
 
Geecon09: SOLID Design Principles
Geecon09: SOLID Design PrinciplesGeecon09: SOLID Design Principles
Geecon09: SOLID Design PrinciplesBruno Bossola
 
Object Oriented Design Principles
Object Oriented Design PrinciplesObject Oriented Design Principles
Object Oriented Design PrinciplesThang Tran Duc
 
Do we need SOLID principles during software development?
Do we need SOLID principles during software development?Do we need SOLID principles during software development?
Do we need SOLID principles during software development?Anna Shymchenko
 
The Open Closed Principle - Part 1 - The Original Version
The Open Closed Principle - Part 1 - The Original VersionThe Open Closed Principle - Part 1 - The Original Version
The Open Closed Principle - Part 1 - The Original VersionPhilip Schwarz
 
SOLID principles-Present
SOLID principles-PresentSOLID principles-Present
SOLID principles-PresentQuang Nguyen
 
Clean code quotes - Citações e provocações
Clean code quotes - Citações e provocaçõesClean code quotes - Citações e provocações
Clean code quotes - Citações e provocaçõesAndré de Fontana Ignacio
 
SOLID, DRY, SLAP design principles
SOLID, DRY, SLAP design principlesSOLID, DRY, SLAP design principles
SOLID, DRY, SLAP design principlesSergey Karpushin
 

What's hot (20)

Solid principles of oo design
Solid principles of oo designSolid principles of oo design
Solid principles of oo design
 
SOLID Principles
SOLID PrinciplesSOLID Principles
SOLID Principles
 
Implementing The Open/Closed Principle
Implementing The Open/Closed PrincipleImplementing The Open/Closed Principle
Implementing The Open/Closed Principle
 
Relax, it's spa time
Relax, it's spa timeRelax, it's spa time
Relax, it's spa time
 
SOLID Principles part 1
SOLID Principles part 1SOLID Principles part 1
SOLID Principles part 1
 
SOLID Design principles
SOLID Design principlesSOLID Design principles
SOLID Design principles
 
Refactoring Applications using SOLID Principles
Refactoring Applications using SOLID PrinciplesRefactoring Applications using SOLID Principles
Refactoring Applications using SOLID Principles
 
"SOLID" Object Oriented Design Principles
"SOLID" Object Oriented Design Principles"SOLID" Object Oriented Design Principles
"SOLID" Object Oriented Design Principles
 
Refactoring to SOLID Code
Refactoring to SOLID CodeRefactoring to SOLID Code
Refactoring to SOLID Code
 
S.O.L.I.D. Principles for Software Architects
S.O.L.I.D. Principles for Software ArchitectsS.O.L.I.D. Principles for Software Architects
S.O.L.I.D. Principles for Software Architects
 
Geecon09: SOLID Design Principles
Geecon09: SOLID Design PrinciplesGeecon09: SOLID Design Principles
Geecon09: SOLID Design Principles
 
SOLID principles
SOLID principlesSOLID principles
SOLID principles
 
Object Oriented Design Principles
Object Oriented Design PrinciplesObject Oriented Design Principles
Object Oriented Design Principles
 
Do we need SOLID principles during software development?
Do we need SOLID principles during software development?Do we need SOLID principles during software development?
Do we need SOLID principles during software development?
 
IoC and Mapper in C#
IoC and Mapper in C#IoC and Mapper in C#
IoC and Mapper in C#
 
The Open Closed Principle - Part 1 - The Original Version
The Open Closed Principle - Part 1 - The Original VersionThe Open Closed Principle - Part 1 - The Original Version
The Open Closed Principle - Part 1 - The Original Version
 
SOLID principles-Present
SOLID principles-PresentSOLID principles-Present
SOLID principles-Present
 
Clean code quotes - Citações e provocações
Clean code quotes - Citações e provocaçõesClean code quotes - Citações e provocações
Clean code quotes - Citações e provocações
 
SOLID, DRY, SLAP design principles
SOLID, DRY, SLAP design principlesSOLID, DRY, SLAP design principles
SOLID, DRY, SLAP design principles
 
Solid js
Solid jsSolid js
Solid js
 

Similar to Writing Maintainable Software Using SOLID Principles

Building Your First App with MongoDB
Building Your First App with MongoDBBuilding Your First App with MongoDB
Building Your First App with MongoDBMongoDB
 
Object Oriented Concepts and Principles
Object Oriented Concepts and PrinciplesObject Oriented Concepts and Principles
Object Oriented Concepts and Principlesdeonpmeyer
 
Building apps with tuscany
Building apps with tuscanyBuilding apps with tuscany
Building apps with tuscanyLuciano Resende
 
The Next Five Years of Rails
The Next Five Years of RailsThe Next Five Years of Rails
The Next Five Years of RailsAlex Mercer
 
Beginning MEAN Stack
Beginning MEAN StackBeginning MEAN Stack
Beginning MEAN StackRob Davarnia
 
Architecting single-page front-end apps
Architecting single-page front-end appsArchitecting single-page front-end apps
Architecting single-page front-end appsZohar Arad
 
Pavlo Zhdanov "Mastering solid and base principles for software design"
Pavlo Zhdanov "Mastering solid and base principles for software design"Pavlo Zhdanov "Mastering solid and base principles for software design"
Pavlo Zhdanov "Mastering solid and base principles for software design"LogeekNightUkraine
 
Angular presentation
Angular presentationAngular presentation
Angular presentationMatus Szabo
 
SQL to NoSQL: Top 6 Questions
SQL to NoSQL: Top 6 QuestionsSQL to NoSQL: Top 6 Questions
SQL to NoSQL: Top 6 QuestionsMike Broberg
 
Microservices in Golang
Microservices in GolangMicroservices in Golang
Microservices in GolangMo'ath Qasim
 
Jmp206 Web Services Bootcamp Final Draft
Jmp206   Web Services Bootcamp Final DraftJmp206   Web Services Bootcamp Final Draft
Jmp206 Web Services Bootcamp Final DraftBill Buchan
 
Backend Development Bootcamp - Node [Online & Offline] In Bangla
Backend Development Bootcamp - Node [Online & Offline] In BanglaBackend Development Bootcamp - Node [Online & Offline] In Bangla
Backend Development Bootcamp - Node [Online & Offline] In BanglaStack Learner
 
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQL
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQLHTTP, JSON, JavaScript, Map&Reduce built-in to MySQL
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQLUlf Wendel
 
Build an App with Blindfold - Britt Barak
Build an App with Blindfold - Britt Barak Build an App with Blindfold - Britt Barak
Build an App with Blindfold - Britt Barak DroidConTLV
 
Write Once, Run Everywhere - Ember.js Munich
Write Once, Run Everywhere - Ember.js MunichWrite Once, Run Everywhere - Ember.js Munich
Write Once, Run Everywhere - Ember.js MunichMike North
 

Similar to Writing Maintainable Software Using SOLID Principles (20)

Building Your First App with MongoDB
Building Your First App with MongoDBBuilding Your First App with MongoDB
Building Your First App with MongoDB
 
Http and REST APIs.
Http and REST APIs.Http and REST APIs.
Http and REST APIs.
 
Object Oriented Concepts and Principles
Object Oriented Concepts and PrinciplesObject Oriented Concepts and Principles
Object Oriented Concepts and Principles
 
Building apps with tuscany
Building apps with tuscanyBuilding apps with tuscany
Building apps with tuscany
 
Cloud Computing2
Cloud Computing2Cloud Computing2
Cloud Computing2
 
The Next Five Years of Rails
The Next Five Years of RailsThe Next Five Years of Rails
The Next Five Years of Rails
 
Day01 api
Day01   apiDay01   api
Day01 api
 
Beginning MEAN Stack
Beginning MEAN StackBeginning MEAN Stack
Beginning MEAN Stack
 
Architecting single-page front-end apps
Architecting single-page front-end appsArchitecting single-page front-end apps
Architecting single-page front-end apps
 
Pavlo Zhdanov "Mastering solid and base principles for software design"
Pavlo Zhdanov "Mastering solid and base principles for software design"Pavlo Zhdanov "Mastering solid and base principles for software design"
Pavlo Zhdanov "Mastering solid and base principles for software design"
 
Angular presentation
Angular presentationAngular presentation
Angular presentation
 
SQL to NoSQL: Top 6 Questions
SQL to NoSQL: Top 6 QuestionsSQL to NoSQL: Top 6 Questions
SQL to NoSQL: Top 6 Questions
 
Microservices in Golang
Microservices in GolangMicroservices in Golang
Microservices in Golang
 
Jmp206 Web Services Bootcamp Final Draft
Jmp206   Web Services Bootcamp Final DraftJmp206   Web Services Bootcamp Final Draft
Jmp206 Web Services Bootcamp Final Draft
 
Backend Development Bootcamp - Node [Online & Offline] In Bangla
Backend Development Bootcamp - Node [Online & Offline] In BanglaBackend Development Bootcamp - Node [Online & Offline] In Bangla
Backend Development Bootcamp - Node [Online & Offline] In Bangla
 
working with PHP & DB's
working with PHP & DB'sworking with PHP & DB's
working with PHP & DB's
 
NodeJS @ ACS
NodeJS @ ACSNodeJS @ ACS
NodeJS @ ACS
 
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQL
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQLHTTP, JSON, JavaScript, Map&Reduce built-in to MySQL
HTTP, JSON, JavaScript, Map&Reduce built-in to MySQL
 
Build an App with Blindfold - Britt Barak
Build an App with Blindfold - Britt Barak Build an App with Blindfold - Britt Barak
Build an App with Blindfold - Britt Barak
 
Write Once, Run Everywhere - Ember.js Munich
Write Once, Run Everywhere - Ember.js MunichWrite Once, Run Everywhere - Ember.js Munich
Write Once, Run Everywhere - Ember.js Munich
 

Recently uploaded

Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 

Recently uploaded (20)

Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 

Writing Maintainable Software Using SOLID Principles

  • 1. Writing Maintainable Software using SOLID Principles Doug Jones duggj@yahoo.com
  • 2. Have you ever played Jenga? From <http://www.codemag.com/article/1001061>
  • 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. 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. 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. 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; }
  • 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. 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. 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. 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. SRP: Single Responsibility Principle A class should have only one reason to change SOLID
  • 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. 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. 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. 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. 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. 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
  • 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. 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. Chuck Norris, Jon Skeet, and Immutability (readonly keyword in C#)  Chuck Norris doesn’t read books…  Jon Skeet is immutable… SOLID
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  • 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. 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. 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. 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. 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. 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
  • 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. 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. 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. Create another strategy for business rules public class ExtraditionUserScamEligibility: IUserScamEligibility { public bool IsUserScamEligible(UserContact user) { return !CountryLikelyToSeekExtradition(user.Country); } SOLID
  • 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. 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. Just change 2 lines of code… public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IUserScamEligibility, ExtraditionUserScamEligibility>(); services.AddSingleton<IUserMessageSender, TextUserMessageSender>(); … SOLID
  • 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. 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. The UPS as a Decorator Mark Seemann. Dependency Injection in .NET
  • 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. 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. .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. …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. 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. 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. New instance every call using Abstract Factory public class Sha512HashAlgorithmFactory : IHashAlgorithmFactory { public HashAlgorithm GetInstance() { return System.Security.Cryptography.SHA512.Create(); } } SOLID
  • 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. DI just needed additional registrations… services.AddSingleton<IUserMessageSender,SmtpUserMessageSender>(); services.AddSingleton<IUserMessageSender, TextUserMessageSender>(); services.AddSingleton<IUserMessageSenderFactory, UserMessageSenderFactory>(); SOLID
  • 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. 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. 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. 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
  • 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. 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. 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. …and when I try to serialize my User… another LSP violation private string SerializeUser(User user) { return _serializer.SerializeObject(user); } SOLID
  • 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
  • 74. Interface with multiple roles public interface IMultiRoleUserRepository { UserContact GetUserContactById(int userId); void InsertUser(User user); void UpdateEmailAddress(int userId, string emailAddress); } SOLID
  • 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. 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. 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. 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. 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. 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. …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
  • 83. …into a well built structure that’s easy to maintain
  • 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. Writing Maintainable Software using SOLID Principles Doug Jones duggj@yahoo.com Questions?

Editor's Notes

  1. WET – Write Everything Twice