SOLID PRINCIPLE
IMPLEMENTATION
LETS RECAP
S
O
L
I
D
SINGLE RESPONSIBILITY PRINCIPLE OPEN CLOSE PRINCIPLE LISKOV SUBSTITUTION PRINCIPLE INTERFACE SEGREGATION PRINCIPLE DEPENDENCY INVERSION PRINCIPLE
LETS RECAP
SINGLE RESPOSIBILITY
-
A CLASS SHOULD HAVE ONE AND ONLY ONE REASON TO CHANGE
class Customer
{
void Add(Database db)
{
try
{
db.Add();
}
catch (Exception ex)
{
File.WriteAllText(@"C:\Error.txt", ex.ToString());
}
}
}
class Customer
{
private FileLogger logger = new FileLogger();
void Add(Database db)
{
try {
db.Add();
}
catch (Exception ex)
{
logger.Handle(ex.ToString());
}
}
}
class FileLogger
{
void Handle(string error)
{
File.WriteAllText(@"C:\Error.txt", error);
}
}
Bad Way
This Add method does too much, it shouldn’t know how to write to the log and how to add a customer.
Good Way
This doesn’t violate the single responsibility principle by abstracting the logger for the actual writing.
LETS RECAP
OPEN CLOSE
-
ENTITIES SHOULD BE OPEN FOR EXTENSION BUT CLOSE FOR MODIFICATION
class Customer
{
int Type;
void Add(Database db)
{
if (Type == 0)
{
db.Add();
}
else
{
db.AddExistingCustomer();
}
}
}
Bad Way
This violates the Open Closed Principle, because at the moment there are 2 types of customer, if we want to add another customer type we have to add an if else statement below and will modify the existing code.
class CustomerBetter
{
void Add(Database db)
{
db.Add();
}
}
class ExistingCustomer : CustomerBetter
{
override void Add(Database db)
{
db.AddExistingCustomer();
}
}
class AnotherCustomer : CustomerBetter
{
override void Add(Database db)
{
db.AnotherExtension();
}
}
Good Way
This is better, because we structure the code so it’s easier to extend and harder to modify.
LETS RECAP
LISKOV SUBSTITUTION
-
PRECONDITION CANNOT BE STRENGTHENED IN A SUBTYPE
-
POSTCONDITION CANNOT BE WEAKENED IN A SUBTYPE
-
INVARIANTS OF THE SUPER-TYPE MUST BE PRESERVED IN A SUBTYPE
-
FUNCTION THAT USE POINTERS OF REFERENCE TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASS WITHOUT KNOWING IT
class Enquiry : Customer
{
override int Discount(int sales)
{
return sales * 5;
}
override void Add(Database db)
{
throw new Exception("Not allowed");
}
}
class BetterGoldCustomer : Customer
{
override int Discount(int sales)
{
return sales - 100;
}
}
class BetterSilverCustomer : Customer
{
override int Discount(int sales)
{
return sales - 50;
}
}
// e.g. to show how this is bad:
class ViolatingLiskovs
{
void ParseCustomers()
{
var database = new Database();
var customers = new List<Customer>
{
new GoldCustomer(),
new SilverCustomer(),
new Enquiry() // This is valid, but...
};
foreach (Customer c in customers)
{
// Enquiry.Add() will throw an exception here!
c.Add(database);
}
}
}
interface IDiscount {
int Discount(int sales);
}
interface IDatabase {
void Add(Database db);
}
internal class Customer : IDiscount, IDatabase
{
int Discount(int sales) { return sales; }
void Add(Database db) { db.Add(); }
}
// GOOD: Now, we don't violate LSP
class AdheringToLiskovs
{
public void ParseCustomers()
{
var database = new Database();
var customers = new List<Customer>
{
new GoldCustomer(),
new SilverCustomer(),
new Enquiry() // This will give a compiler error,
// rather than runtime error
};
foreach (Customer c in customers)
{
// Enquiry.Add() will throw an exception here!
// But, we won't get to here as compiler will complain
c.Add(database);
}
}
}
we don’t want this to add an enquiry so we have to throw a new exception, that violates the principle.
Good Way
Bad Way
LETS RECAP
INTERFACE SEGREGATION
-
A CLIENT SHOULD NOT BE FORCED TO IMPLEMENT AN INTERFACE THAT IT DOES NOT USE
interface ICustomer // existing
{
void Add();
}
interface ICustomerImproved
{
void Add();
void Read(); // Existing Functionality, BAD
}
interface ICustomerV1 : ICustomer
{
void Read();
}
class CustomerWithRead : ICustomer, ICustomerV1
{
void Add()
{
var customer = new Customer();
customer.Add(new Database());
}
void Read()
{
// GOOD: New functionality here!
}
}
// e.g.
void ManipulateCustomers()
{
var database = new Database();
var customer = new Customer();
customer.Add(database); // Old functionality, works fine
var readCustomer = new CustomerWithRead();
readCustomer.Read(); // Good! New functionalty is separate from existing customers
}
Bad Way
If we want to add more functionality, don’t add to existing interfaces, segregate them out.
Good Way
Just create another interface, that a class can also extend from.
LETS RECAP
DEPENDENCY INVERSION
-
HIGH-LEVEL MODULES SHOULD NOT DEPEND ON LOW-LEVEL MODULES.
-
BOTH SHOULD DEPEND ON ABSTRACTION
-
ABSTRACTION SHOULD NOT DEPEND ON DETAIL
-
DETAIL SHOULD DEPEND ON ABSTRACTION
class FileLogger
{
void Handle(string error)
{
File.WriteAllText(@"C:\Error.txt", error);
}
}
internal class Customer
{
FileLogger logger = new FileLogger(); // Bad
public void Add(Database db)
{
try
{
db.Add();
}
catch (Exception error)
{
logger.Handle(error.ToString());
}
}
}
class BetterCustomer
{
ILogger logger;
BetterCustomer(ILogger logger)
{
this.logger = logger;
}
void Add(Database db)
{
try
{
db.Add();
}
catch (Exception error)
{
logger.Handle(error.ToString());
}
}
}
class EmailLogger : ILogger
{
void Handle(string error)
{
File.WriteAllText(@"C:\Error.txt", error);
}
}
interface ILogger
{
void Handle(string error);
}
// e.g. when it is used:
void UseDependencyInjectionForLogger()
{
var customer = new BetterCustomer(new EmailLogger());
customer.Add(new Database());
}
Bad Way
We are relying on the customer to say that we are using a File Logger, rather than another type of logger, e.g. EmailLogger.
Good Way
We pass in a Logger interface to the customer so it doesn’t know what type of logger it is.
MY INTIAL CODE
namespace Geomatic.MapTool.WorkAreaTools
{
public class CreateWorkAreaTool : AddTool
{
private IGeometry geometry;
public CreateWorkAreaTool()
{
_name = "Create Work Area";
}
public override void OnClick()
{
base.OnClick();
OnStepReported("Draw a working a area. Double click to end drawing.");
}
public override void OnMouseDown(int button, int shift, int x, int y)
{
IPoint point = DisplayTransformation.ToMapPoint(x, y);
if (InProgress)
{
return;
}
if (button == MouseKey.Left)
{
try
{
InProgress = true;
RepositoryFactory repo = new RepositoryFactory(SegmentName);
geometry = new PolygonTracker().TrackNew(ActiveView, point);
List<GWorkArea> area;
List<GStreet> street;
List<GProperty> property;
List<GJunction> junction;
List<GBuilding> building;
List<GLandmark> landmark;
List<GBuildingGroup> buildingGroup;
List<GRegion> region;
using (new WaitCursor())
{
if (geometry == null)
{
return;
}
area = repo.SpatialSearch<GWorkArea>(geometry, esriSpatialRelEnum.esriSpatialRelIntersects).ToList();
//Checked for whether there is any overlap working area. if yes, then cannot create the working area.
if (area.Count > 0)
{
Reset();
throw new Exception("Area locked ! Current area is being used." + "\n" + area[0].ToString());
}
street = repo.SpatialSearch<GStreet>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
property = repo.SpatialSearch<GProperty>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
junction = repo.SpatialSearch<GJunction>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
building = repo.SpatialSearch<GBuilding>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
landmark = repo.SpatialSearch<GLandmark>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
buildingGroup = repo.SpatialSearch<GBuildingGroup>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
region = repo.SpatialSearch<GRegion>(geometry, esriSpatialRelEnum.esriSpatialRelIntersects).ToList();
int featureCount = street.Count() + property.Count() + junction.Count() + building.Count() + landmark.Count() + buildingGroup.Count();
//Checked if featureCOunt more than 500. if yes, then cannot create the working area.
if (featureCount > 500)
{
Reset();
throw new Exception("Please draw a smaller area. Feature count exceed 500." + "\nFeature Count = " + featureCount);
}
IList<string> regionIndex = new List<string>();
if (region.Count > 0)
{
region.ForEach(reg => regionIndex.Add(reg.index.Replace(" ", "_") + ".lnd"));
}
try
{
using (new WaitCursor())
{
repo.StartTransaction();
}
OnStepReported("Feature Included = " + featureCount);
string wo_num;
using (UI.Forms.WorkOrderForm form = new UI.Forms.WorkOrderForm())
{
if (form.ShowDialog() != DialogResult.OK)
{
throw new ProcessCancelledException();
}
using (new WaitCursor())
{
form.SetIndexArray(regionIndex);
wo_num = form.SubmitToWorms();
OnStepReported("Submitted to WOMS");
}
}
GWorkArea newArea = repo.NewObj<GWorkArea>();
newArea.Wo_no = wo_num;
newArea.DateCreated1 = DateTime.Now.ToString("yyyy-MM-dd");
newArea.DateUpdated1 = DateTime.Now.ToString("yyyy-MM-dd");
newArea.Shape = geometry;
newArea.user_id = Session.User.Name;
newArea.WorkAreaSegmentName = SegmentName.ToString();
newArea = repo.Insert(newArea, false);
street = repo.SpatialSearch<GStreet>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
property = repo.SpatialSearch<GProperty>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
junction = repo.SpatialSearch<GJunction>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
building = repo.SpatialSearch<GBuilding>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
landmark = repo.SpatialSearch<GLandmark>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
buildingGroup = repo.SpatialSearch<GBuildingGroup>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
int currentCount = 0;
using (UI.Forms.ProgressForm form = new UI.Forms.ProgressForm())
{
form.setMinimun(0);
form.setMaximum(featureCount);
form.Show();
street.ForEach(str => {
str.AreaId = newArea.OID;
repo.Update(str);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
property.ForEach(prop => {
prop.AreaId = newArea.OID;
repo.Update(prop);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
junction.ForEach(junc => {
junc.AreaId = newArea.OID;
repo.Update(junc);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
building.ForEach(build => {
build.AreaId = newArea.OID;
repo.Update(build);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
landmark.ForEach(lnd => {
lnd.AreaId = newArea.OID;
repo.Update(lnd);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
buildingGroup.ForEach(buildGrp => {
buildGrp.AreaId = newArea.OID;
repo.Update(buildGrp);
currentCount = currentCount + 1;
form.setValue(currentCount);
});
}
MessageBox.Show("Work Order No : " + wo_num + "\n" + "Working Area No : " + newArea.OID);
repo.EndTransaction();
using (new WaitCursor())
{
Session.Current.Cadu.Save(true);
}
OnStepReported("Draw a working a area. Double click to end drawing.");
}
catch (Exception ex)
{
using (MessageBoxBuilder box = MessageBoxFactory.Create(ex))
{
box.Show();
}
repo.AbortTransaction();
OnStepReported("Draw a working a area. Double click to end drawing.");
}
}
InProgress = false;
}
catch (Exception ex)
{
using (MessageBoxBuilder box = MessageBoxFactory.Create(ex))
{
box.Show();
}
}
}
else if (button == MouseKey.Right)
{
MapDocument.ShowContextMenu(x, y);
}
}
protected override void Reset()
{
geometry = null;
InProgress = false;
}
}
}
WHAT PRINCIPLE DOES IT BREAK?
LETS DISCUSS IT TOGETHER..
MY FINAL CODE
namespace Geomatic.MapTool.WorkAreaTools
{
public class CreateWorkAreaTool : AddTool
{
private IGeometry geometry;
public CreateWorkAreaTool()
{
_name = "Create Work Area";
}
public override void OnClick()
{
base.OnClick();
OnStepReported("Draw a working a area. Double click to end drawing.");
}
public override void OnMouseDown(int button, int shift, int x, int y)
{
IPoint point = DisplayTransformation.ToMapPoint(x, y);
if (InProgress)
{
return;
}
if (button == MouseKey.Left)
{
try
{
InProgress = true;
RepositoryFactory repo = new RepositoryFactory(SegmentName);
geometry = new PolygonTracker().TrackNew(ActiveView, point);
using (new WaitCursor())
{
if (geometry == null){return;}
List<GWorkArea> area = repo.SpatialSearch<GWorkArea>(geometry, esriSpatialRelEnum.esriSpatialRelIntersects).ToList();
CheckForAreaOverlap(area);
var FeaturesInWorkArea = GetFeaturesInWorkArea(repo, geometry);
int featureCount = GetFeaturesInWorkAreaCount(FeaturesInWorkArea);
CheckIfFeatureCountExceed500(featureCount);
IList<string> regionIndex = PrepareRegionIndex(repo, geometry);
try
{
using (new WaitCursor())
{
repo.StartTransaction();
}
string wo_num = SubmitToWOMS(regionIndex);
GWorkArea newArea = CreateWorkArea(repo, wo_num);
UpdateAreaIdForFeaturesInWorkArea(FeaturesInWorkArea, repo, newArea.OID);
MessageBox.Show("Work Order No : " + wo_num + "\n" + "Working Area No : " + newArea.OID);
repo.EndTransaction();
using (new WaitCursor())
{
Session.Current.Cadu.Save(true);
}
OnStepReported("Draw a working a area. Double click to end drawing.");
}
catch (Exception ex)
{
using (MessageBoxBuilder box = MessageBoxFactory.Create(ex))
{
box.Show();
}
repo.AbortTransaction();
OnStepReported("Draw a working a area. Double click to end drawing.");
}
}
InProgress = false;
}
catch (Exception ex)
{
using (MessageBoxBuilder box = MessageBoxFactory.Create(ex))
{
box.Show();
}
}
}
else if (button == MouseKey.Right)
{
MapDocument.ShowContextMenu(x, y);
}
}
protected override void Reset()
{
geometry = null;
InProgress = false;
}
private void CheckForAreaOverlap(List<GWorkArea> area)
{
//Checked for whether there is any overlap working area. if yes, then cannot create the working area.
if (area.Count > 0)
{
Reset();
throw new Exception("Area locked ! Current area is being used." + "\n" + area[0].ToString());
}
}
private Tuple<List<GStreet>, List<GProperty>, List<GJunction>,
List<GBuilding>, List<GLandmark>, List<GBuildingGroup>> GetFeaturesInWorkArea(RepositoryFactory repo, IGeometry geometry)
{
List<GStreet> street = repo.SpatialSearch<GStreet>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
List<GProperty> property = repo.SpatialSearch<GProperty>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
List<GJunction> junction = repo.SpatialSearch<GJunction>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
List<GBuilding> building = repo.SpatialSearch<GBuilding>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
List<GLandmark> landmark = repo.SpatialSearch<GLandmark>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
List<GBuildingGroup> buildingGroup = repo.SpatialSearch<GBuildingGroup>(geometry, esriSpatialRelEnum.esriSpatialRelContains).ToList();
return new Tuple<List<GStreet>, List<GProperty>, List<GJunction>, List<GBuilding>, List<GLandmark>, List<GBuildingGroup>>
(street, property, junction, building, landmark, buildingGroup);
}
private int GetFeaturesInWorkAreaCount(Tuple<List<GStreet>, List<GProperty>, List<GJunction>,
List<GBuilding>, List<GLandmark>, List<GBuildingGroup>> features)
{
int totalFeatures = features.Item1.Count() + features.Item2.Count() + features.Item3.Count() + features.Item4.Count()
+ features.Item5.Count() + features.Item6.Count();
return totalFeatures;
}
private void CheckIfFeatureCountExceed500(int featureCount)
{
//Checked if featureCOunt more than 500. if yes, then cannot create the working area.
if (featureCount > 500)
{
Reset();
throw new Exception("Please draw a smaller area. Feature count exceed 500." + "\nFeature Count = " + featureCount);
}
}
private IList<string> PrepareRegionIndex(RepositoryFactory repo, IGeometry geometry)
{
List<GRegion> region = repo.SpatialSearch<GRegion>(geometry, esriSpatialRelEnum.esriSpatialRelIntersects).ToList();
IList<string> regionIndex = new List<string>();
if (region.Count > 0)
{
region.ForEach(reg => regionIndex.Add(reg.index.Replace(" ", "_") + ".lnd"));
}
return regionIndex;
}
private string SubmitToWOMS(IList<string> regionIndex)
{
using (UI.Forms.WorkOrderForm form = new UI.Forms.WorkOrderForm())
{
if (form.ShowDialog() != DialogResult.OK)
{
throw new ProcessCancelledException();
}
using (new WaitCursor())
{
form.SetIndexArray(regionIndex);
string wo_num = form.SubmitToWorms();
OnStepReported("Submitted to WOMS");
return wo_num;
}
}
}
private GWorkArea CreateWorkArea(RepositoryFactory repo, string wo_num)
{
GWorkArea newArea = repo.NewObj<GWorkArea>();
newArea.Wo_no = wo_num;
newArea.DateCreated1 = DateTime.Now.ToString("yyyy-MM-dd");
newArea.DateUpdated1 = DateTime.Now.ToString("yyyy-MM-dd");
newArea.Shape = geometry;
newArea.user_id = Session.User.Name;
newArea.WorkAreaSegmentName = SegmentName.ToString();
return newArea = repo.Insert(newArea, false);
}
private void UpdateAreaIdForFeaturesInWorkArea(Tuple<List<GStreet>, List<GProperty>, List<GJunction>,
List<GBuilding>, List<GLandmark>, List<GBuildingGroup>> features, RepositoryFactory repo, int areaId)
{
features.Item1.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
features.Item2.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
features.Item3.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
features.Item4.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
features.Item5.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
features.Item6.ForEach(feature => {
feature.AreaId = areaId;
repo.Update(feature);
});
}
}
}
solid principle
By Syafiq bin abdul rahman
solid principle
- 147