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

  1. 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

  1. 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

  1. PRECONDITION CANNOT BE STRENGTHENED IN A SUBTYPE  
  2. POSTCONDITION CANNOT BE WEAKENED IN A SUBTYPE
  3. INVARIANTS OF THE SUPER-TYPE MUST BE PRESERVED IN A SUBTYPE
  4. 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

  1. 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

  1. HIGH-LEVEL MODULES SHOULD NOT DEPEND ON LOW-LEVEL MODULES.
  2. BOTH SHOULD DEPEND ON ABSTRACTION
  3. ABSTRACTION SHOULD NOT DEPEND ON DETAIL
  4. 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