Encapsulation and SOLID Plural Site Lecture Notes
http://www.c-sharpcorner.com/UploadFile/damubetha/solid-principles-in-C-Sharp/
SOLID
Single Responsibility PrincipleOpen Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
-Code that is not up to standard will have par effects on
- Long Term Productivity
- Maintainability (you may get version 1 out of the door, but if you've built up technical debt future releases suffer)
- the point of creating solid code is to make code so supple, so apply-able, that when requirements change you can easily change the code to match the new requirements.
SOLID directs you to a more OO design.
Purpose of SOLID is to make you more productive by making code more maintainable through decomposition and decoupling.
Helps defend against a
- Rigid design (the design is difficult to change)
- Fragility - code is easy to break
- Immobility - The design is difficult to reuse.
- Viscosity - difficult to do the right thing
- Needless Complexity - prevents over-design
Single Responsibility Principle
Classes should only have a single responsibility.- 'responsibility' meaning 'reason for change'
- single responsibility meaning a class should only have a single reason to change.
- separation of concerns - a class should do one thing and do it well.
Open Closed Principle
- class should be open for extensibility, but closed for modification, essentially what this means is that when you have written a class and put the class in production or other clients rely on that class you are no longer allowed to make changes to that class. instead if you want to redefine the behavior of that class, it should be open for extensibility, which means that anyone should be able to extend the class in order to redefine parts of its behavior. the only exception to the rule that you are not allowed to make changes through classes that are already used, except for when you find bugs in that class. bug fixes are ok, but other then that you are not really allowed to make changes to that class when its being used by other clients.
- Favor composition of inheritance
Liskov Substitution Principle
- The LSP talks about polymorphism and that an important aspect of understanding how to go about composition.
- Favor composition of inheritance
- Sub-types must be substitute-able for their base types
- A client should be able to consume any implementation of a given interface without changing the correctness of the system. (correctness of the system - meaning the correct behavior of the system)
- The formal definition of Liskov Substitution Principle states that: If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program.
eg. if you have a client that talks to version-a of an interface and that does not cause the entire system to crash, if that client changes to talk to an implementation b of the same interface, and that causes the system to crash, then you have changed the correctness of the system, but if the implementation b of the interface also doesn't cause the entire system to crash, then you probably haven't changed the correctness of the system.
The Interface Segregation Principle
-The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.[1] ISP splits interfaces which are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. Such shrunken interfaces are also called role interfaces
- clients should not be forced to depend on methods they do not use.
- who owns/defines the interface? A client owns/defines the interface, not the concrete classes that implement those interfaces. its the client that needs the interface. so the client owns the interface and the client defines what it needs, there is no reason for a client to define a member of an interface if it doesn't use that member. so remember interfaces arnt defined by the concrete class that implements the interface, they are defined by classes that consume them interface.
-favor
role interfaces (interfaces defined by client for a certain behavior)
over
header interfaces (interfaces defined for concrete classes i,e a entity/DTO with loads of properties)
- will help with the violations of the Liskov Substitution Principle
Dependency Inversion Principle
=================================================================
Encapsulation
-Compartmentalizing a classes behavior elements from its contracted structural elements .
-Encapsulation serves to separate the contractual interface of an abstraction and its implementation
- The process of compartmentalizing the elements of an abstraction that constitute its structure and behavior;
-Concentrate on building a class based on structural elements, and encapsulating the varying behavior
elements. Separating\
- Information hiding (implementation hiding)
- shifting the code's dependency on an uncertain implementation (design decision) onto a well-defined interface. hiding the implementation details.
Command Query Separation Principle (postels law) - architectural pattern - oo design
Command Query Separation -> "Your operations should be either commands or queries, but not both"
- Command is an operation that has an observable side affect in the system (i.e. they mutate a state) .
ie. a save operation is a command as the side effect would be that the saving the data into a database.
-- should return void
- Query is an operation that returns data. (doesn't not mutate state)
-- should return a type
Avoid creating an operation that triggers side effects and also returns data.
i.e Never have a single operation mutates a state and returns data
code example
public class FileStore
{
public string WorkingDirectory {get;set;} <- property value is used by save and read operations
public string Save(int id, string message) //-- is a command-query, saved the message and returned a file name - this is what we're trying to avoid,
public EventHandler<MessageEventArgs> MessageRead; // returns message
public void Read (int id);
}
the problem with the "save method" is that we straight away don't understand what the return string value is for, this affects readability for a programmer as he/she will need to look into the code to figure it out. Its been noted that we spend 10x more time reading code then writing code, by following the command query separation query principle like below, we can easily shorten the time a fellow programmer takes to understand our code.
// how we seperate the above "save" command-query operation
public class FileStore
{
public string WorkingDirectory {get;set;}
public void Save(int fileId, string message) <-- a command clearly states it just saves\
{
var path = this.GetFileName(fileId);
File.WriteAllText(path,message);
}
public string Read (int fileId)
{
var path = this.GetFileName(fileId);
var msg = File.ReadAllText(path);
return msg;
}
public string GetFileName(int fileId)<-- a query that clearly states that it returns a file name.
{
return Path.Combine(this.WorkingDirectory, fileId + . ".txt")
}
}
Question!! given the implementation we have above, the inputs and the outputs, focusing on the inputs, is there anything that can go wrong here, given the various inputs. the answer is yes, as the WorkingDirectory property which is used by save and read operations can be null, it will throw an error on all operations if its not provide, which is probably. how can we guard against this invalid object state, one step would be to create a constructor that takes a WorkingDirectory as an input. yet there is still two ways the WorkingDirectory can still be null, the first that the WorkingDirectory property that has a public setter, so its possible to assign null to that setting, we need to make the setter private, so it can only internally be set. The second is that its still possible to invoke the constructor with a null value though the constructor, as a string datatype is a reference type and allows for nulls. In order to protect us against that, we need to add a guard clause, a guard clause is something that only valuates whether or not working directory is null or not at run-time and it fails fast, if it turns out that working directory is null.
we should also fail fast if they provide an invalid working directory string.
// how we seperate the above "save" command-query operation
public class FileStore
{
public string WorkingDirectory {get; private set;}
public FileStore(string workingDirectory)
{
if (workingDirectory== null)
throw new ArgumentNullException("workingDirectory);
if (!Directory.Exists(workingDirectory))
{
throw new ArgumentException("working direcorty string does note referend a working
directory","workingDirectory")
}
this.WorkingDirectory = workingDirectory
}
public void Save(int fileId, string message) <-- a command clearly states it just saves\
{
var path = this.GetFileName(fileId);
File.WriteAllText(path,message);
}
public string Read (int fileId)
{
var path = this.GetFileName(fileId);
var msg = File.ReadAllText(path);
return msg;
}
public string GetFileName(int fileId)<-- a query that clearly states that it returns a file name.
{
return Path.Combine(this.WorkingDirectory, fileId + . ".txt")
}
}
Query's should not return null values when operation cant find a result (effects readability)
you should create and make use of an operation that checks if its legal or not to invoke the query operation beforehand .
- the stronger guarantee that something will be returned, the easier your API will be able to be consumed by a client programmer.
scenario - trying to getting a person object via id, but Id passed in may not associated person in db.
you can do this via the
tester/doer way
if (doesPersonExist(id)) <---- tester
then { getPerson(id) }
try read way.
public outputObject
if (tryGetPerson(id, out outputObject)) {}
Maybe way (for collections)
return a empty collection
the bottom line of command query separation is that it makes it easier to reason about the code if you strictly follow that principle, so if you can agree with your team, that in a particular code base that you will strictly adhere to the command query separation principle, you will begin to experience that you can trust the code without fully understanding all the implementation details, so if your looking at a piece of code, and you can see that code calls 3 other methods, and you can identify those methods as either commands or queries. and you can also read the names of those method. you will begin to have a high level understanding of how those things interact with each other, without actually having to go through and understand all the implementation details.
Reused abstractions principle
- If you have abstractions, in this case if you have interfaces or abstract base classes, and those abstractions are not being re-used by being implemented by various different concrete classes, then you probably have poor abstractions.
- An Abstraction is the elimination of the irrelevant and the amplification of the essential.
- interfaces are not defined, they are discovered as the system grows.
1. start with the concrete classes
2. discover the abstractions as commonality emerges.
follow the Rule of Three
Rule of three is a code refactoring rule of thumb to decide when a replicated piece of code should be replaced by a new procedure. It states that the code can be copied once, but that when the same code is used three times, it should be extracted into a new procedure.