0

In the following example, I'm refactoring the code by creating additional classes which are taking over some of the responsibilities of the main class(Geometry). Hopefully, following the SoC principle.

Notes:

  • The Geometry class has fields(nodes and radii) that holds the data that is being interpreted into abstract objects such as Point, Arch or Line.
  • Classes Point, Arch and Line inherit from abstract class GeoEntity which has a dependency on Geometry class using dependency injection on it's constructor.

Before refactoring

public class Geometry
{
    private List<Vector2> nodes;
    private Dictionary<int, double>[] radii;

    public void DrawLine() { // Do the magic.}
    public void InsertPoint() { // Do the magic.}
    public void InsertArch() { // Do the magic.}

    public void TranslateNode(double dx, double dy) { // Do the magic.}
    public void TranslateLine(double dx, double dy) { // Do the magic.}

    public void RemoveNode(int index) { // Do the magic.}
    public void RemoveLine(int index) { // Do the magic.}
    public void RemoveArch(int index) { // Do the magic.}

    public void DoSpecialNodeRelatedAction1() { // Do the magic.}
    public void DoSpecialNodeRelatedAction2() { // Do the magic.}
    public void DoSpecialLineRelatedAction(double someValue) { // Do the magic.}
}

After refactoring

public class Geometry
{
    private List<Vector2> nodes;
    private Dictionary<int, double>[] radii;

    public Geometry.Point[] Points { get => // Get them magically. }
    public Geometry.Line[] Lines { get => // Get them magically. }
    public Geometry.Arch[] Arches { get => // Get them magically. }

    public void DrawLine() { // Do the magic.}
    public void InsertPoint() { // Do the magic.}
    public void InsertArch() { // Do the magic.}


    public abstract class GeoEntity
    {
        private readonly Geometry geometry;

        protected GeoEntity(Geometry geometry, int index) 
        {
            this.geometry = geometry;
            this.Index = intex;
        }

        public int Index { get; }

        protected abstract void DoSpecificDeletion();        
        public void Delete()
        {
            DoSpecificDeletion();
            geometry.nodes.Remove(Index);
            
            var exists = radii.TryGetValue(Index, out var kvp);
            if(exists) radii.Remove(Index);
        }
    }

    public class Point : GeoEntity
    {
        internal Point(Geometry geometry, int Index) : 
            base(geometry, index) {}

        protected override void DoSpecificDeletion() { // Do the magic.}
        public void Translate(double dx, double dy) { // Do the magic.}
        public void DoSpecialAction1() { // Do the magic.}
        public void DoSpecialAction2() { // Do the magic.}
    }

    public class Line : GeoEntity
    {
        internal Line(Geometry geometry, int Index) : 
            base(geometry, index) {}

        protected override void DoSpecificDeletion() { // Do the magic.}
        public void Translate(double dx, double dy) { // Do the magic.}
        public void DoSpecialAction(double someValue) { // Do the magic.}
    }

    public class Arch: GeoEntity
    {
        internal Arch(Geometry geometry, int Index) : 
            base(geometry, index) {}

        protected override void DoSpecificDeletion() { // Do the magic.}
    }
}

The refactoring in this case should enforce the SoC principle resulting into a cleaner structure with multiple smaller classes, each responsible to alter the data in Geometry class in their specific way, rather than having all methods defined into Geometry class.

A possible issue that I found is shown in the example:

void GeometryConsumingMethod(Geometry geometry)
{
    var a = geometry.Points[0];
    var b = geometry.Points[0];
    
    a.Delete();
    a.DoSpecialAction1();    // Possible logical error.
    b.DoSpecialAction1();    // Possible logical error.
}

However, I'm not sure if this is acceptable or not from an OOP perspective.

I'm curious if this implementation would lead to more prone to error solution.

Darius
  • 71
  • 5
  • 4
    "is it good" This sounds pretty opinion-based and thus won´t give you a "correct" or "whrong" answer. That makes it a bad fit according to SOs rules. – MakePeaceGreatAgain Aug 11 '20 at 14:19
  • 4
    [Codereview](https://codereview.stackexchange.com/). I am more fine if the child knows its parent and calls `Parent.Delete(this)` rather than `this.Delete()`, though commonly preferable approach is to use event, where child doesn't know parent, but simply rise its event. – Sinatr Aug 11 '20 at 14:26
  • 1
    Storing the `Index` in the GeoEntities is problematic, because if you remove another one from the list, the indexes of entities below will change. I think it should be the Geometry's responsibility to do deletions and to call `DoSpecificDeletion` on the entity it is deleting. You could also have a `Node` object storing a `Vector2` and radii as `double[]`, then you don't need to relate the two by an index and a dictionary. – Olivier Jacot-Descombes Aug 11 '20 at 14:31
  • 1
    To modify the question such that it is less opinion-based implies that I should know that there is a standard way, therefore a "correct" way according to that standard, case which would defeat the purpose of the question. My question is: Is there a standard way? Is there a principle would tell me not to do so? The answer might be something like: "No, because there is this principle which if you do not follow, ..." To understand: [link](https://stackoverflow.com/questions/151051/when-should-i-use-gc-suppressfinalize?noredirect=1&lq=1) How is this question not opinion-based? – Darius Aug 11 '20 at 21:44

0 Answers0