Software Design Patterns

Florian Rappl, Fakultät für Physik, Universität Regensburg

Software Design Patterns

Introduction to modern software architecture

software architecture

Clean code

Introduction

  • Clean code has been made popular by Robert C. Martin
  • In fact clean code tries to use a wide range of techniques to ensure
    • stable and robust programs
    • a short development time for extensions
    • fewer bugs and less maintenance cycles
  • We already learned a lot of techniques that are used with clean code
  • Clean code is also driven by coding conventions and custom conventions

Grades

  • The Clean Code Developer (CCD) initiative tries to embody all concepts
  • The grade of a developer represents his skill / pursuit of these rules
  • In total there are six grades:
    1. red
    2. orange
    3. yellow
    4. green
    5. blue
    6. white

Red

  • Don't Repeat Yourself (DRY)
  • Keep It Simple, Stupid (KISS)
  • Favor Composition over Inheritance (FCoI)
  • Using a version control system (VCS, e.g. git)
  • Be cautious with optimizations
  • Perform extensive root cause analysis
  • Apply simple code refactoring methods

Orange

  • Use a Single Level of Abstraction (SLA)
  • Follow the SRP (see SOLID principles)
  • Define and apply source code conventions
  • Setup automatic integration tests
  • Create a system for tracking issues
  • Structure by Separation Of Concerns (SOC)
  • Actively participate in code reviews and read books

Yellow

  • Use three more SOLID principles:
    1. Interface Segregation Principle (ISP)
    2. Dependency Inversion Principle (DIP)
    3. Liskov Substitution Principle (LSP)
  • Perform automatic unit tests together with a code coverage analysis
  • Create mockups for advanced testing
  • Follow the Information Hiding Principle (IHP)

Green

  • Open Close Principle (OCP) (now SOLID is fully applied)
  • Use a continuous integration system
  • Follow the law of demeter
  • Integrate an Inversion of Control (IoC) container
  • Evaluate code metrics
  • Let classes follow the tell, don't ask principle

Blue

  • Continuous delivery in place
  • Test-driven development by test first
  • Iterative development (small, very robust, yet extensible components)
  • You Ain't Gonna Need It (YAGNI)
  • Structuring applications in pieces, so called modules
  • A class is then a piece of a module
  • This modularity encourages parallel development

White

ccd.png

Information hiding

  • Sometimes called encapsulation (controversial)
  • The storage layout should be hidden and cannot be accessed outside
  • Main advantage: Changing the layout will not change anything else
  • Any kind of method that is implementation specific should be hidden
  • Encapsulation is also a technique for providing a stable interface between multiple systems
  • Some people consider inheritance to break encapsulation

Composition over inheritance

  • Sometimes called Composite Reuse Principle and describes a technique for achieving polymorphic behavior
  • Classes should references to other classes, that implement the desired functionality
  • Therefore any functionality is outsourced and SRP is easily possible
  • The ultimate goal is higher flexibility
  • In other words, has a can be better than an is a relationship
  • However, all methods need to be specified, not just a subset

Example

class GameObject
{
    readonly IVisible _v;
    readonly IUpdatable _u;
    readonly ICollidable _c;

    public GameObject(IVisible v, IUpdatable u, ICollidable c)
    {
        _v = v;
        _u = u;
        _c = c;
    }
 
    public void Update()
    {
        _u.Update();
    }
 
    public void Draw()
    {
        _v.Draw();
    }
 
    public void Collide()
    {
        _c.Collide();
    }
}
interface IVisible
{
    void Draw();
}
class Invisible : IVisible
{
    public void Draw()
    {
    }
}
class Visible : IVisible
{
    public void Draw()
    {
        /* draw model */
    }
}
interface ICollidable
{
    void Collide();
} 
class Solid : ICollidable
{
    public void Collide()
    {
        /* check collisions with object and react */
    }
}
class NotSolid : ICollidable
{
    public void Collide()
    {
    }
}
interface IUpdatable
{
    void Update();
}
class Movable : IUpdatable
{
    public void Update()
    {
        /* move object */
    }
}
class NotMovable : IUpdatable
{
    public void Update()
    {
    }
}
class Player : GameObject
{
    public Player() : base(new Visible(), new Movable(), new Solid())
    {

    }
}
class Smoke : GameObject
{
    public Smoke() : base(new Visible(), new Movable(), new NotSolid())
    {

    }
}
class GameObject {
private:
    VisibilityDelegate* _v;
    UpdateDelegate* _u;
    CollisionDelegate* _c;

public:
    GameObject(VisibilityDelegate* v, UpdateDelegate* u, CollisionDelegate* c) : _v(v), _u(u), _c(c) {
    }

    void Update() { 
        _u->Update(); 
    }

    void Draw() { 
        _v->Draw(); 
    }

    void Collide(GameObject objects[]) { 
        _c->Collide(objects); 
    }
};
class VisibilityDelegate {
public:
    virtual void Draw() = 0;
};
class Invisible : public VisibilityDelegate {
public:
    virtual void Draw() {
    }
};
class Visible: public VisibilityDelegate {
public:
    virtual void Draw() { 
        /* draw model */ 
    }
};
class CollisionDelegate {
public:
    virtual void Collide(GameObject objects[]) = 0;
};
class Solid : public CollisionDelegate {
public:
    virtual void Collide(GameObject objects[]) {
        /* check collisions with object and react */ 
    }
};
class NotSolid : public CollisionDelegate {
public:
    virtual void Collide(GameObject objects[]) {
    }
};
class UpdateDelegate {
public:
    virtual void Update() = 0;
};
class Movable : public UpdateDelegate {
public:
    virtual void Update() { 
        /* move object */ 
    }
};
class NotMovable : public UpdateDelegate {
public:
    virtual void Update() { 
    }
};
class Player : public GameObject {
public:
    Player() : GameObject(new Visible(), new Movable(), new Solid()) {
    }
};
class Smoke : public GameObject {
public:
    Smoke() : GameObject(new Visible(), new Movable(), new NotSolid()) {
    }
};

Law of Demeter

  • A specific case of loose coupling (design guideline)
  • Each class should have only limited knowledge about other classes
  • Only closely related classes should be known
  • No communication with unrelated classes
  • More strictly a class should never call the method of an object contained in another class, i.e. a.b.c() is not allowed, but a.b() is
  • In general a method can only call methods from the current class, one of the parameters or a method of a global variable (application or class)

Single Level of Abstraction

  • Readability is improved by clear structures (e.g. formatting)
  • An important detail is the level of abstraction of a block
  • The class name is one level, the public methods another
  • It is important to never mix levels of abstraction
  • Example: Bit-wise operations should not be mixed with method calls
  • Reason: Reader decides how deep to dive into the code

Code coverage

  • Code coverage describes how many possible paths are tested
  • There are several kinds of coverages:
    • Function coverage (is the function tested?)
    • Statement coverage (is the statement called?)
    • Condition coverage (is any possible value included?)
    • State coverage (have all possible states been reached?)
  • A high indicator (higher 80%) is usually a sign for a well-tested code

Code metrics

  • Measuring the quality of software is usually highly subjective
  • Code metrics give numbers that can be used to estimate the quality
    • Maintainability index (0 to 100, high is better)
    • Cyclomatic complexity (number of different paths)
    • Depth of inheritance (lower is better)
    • Class coupling (lower is better)
    • Lines of code (or number of instructions)
  • Cyclomatic complexity is the most important measurement

IoC container

  • Task: Assembling components from different projects into one app
  • Problem: How to do the wiring? Loose coupling?!
  • Of course inversion of control has to be used (from the DIP) with factory
  • Two choices for such a factory (called container):
    1. Service Locator (SL)
    2. Dependency Injection (DI)
  • The SL has to be called explicitly, DI is an implicit construction (target classes don't need to know how the dependency will be resolved)

Continuous delivery

participant "Delivery team" as A
participant "Version control" as B
participant "Build & unit tests" as C
participant "Automated acceptance tests" as D
participant "User acceptance tests" as E
participant "Release" as F
A -> B: Check in
activate B
deactivate A
B -> C: Trigger
activate C #FF271D
deactivate B
C --> A: Feedback
deactivate C
...
A -> B: Check in
activate B
deactivate A
B -> C: Trigger
activate C #00F900
deactivate B
C -> D: Trigger
activate D #FF271D
deactivate C
D --> A: Feedback
deactivate D
...
A -> B: Check in
activate B
deactivate A
B -> C: Trigger
activate C #00F900
deactivate B
C -> D: Trigger
activate D #00F900
deactivate C
D -> E: Approval
activate E #00F900
D --> A: Feedback
deactivate D
E --> A: Feedback
activate F #00F900
E -> F: Approval
deactivate E

Literature

  • Shalloway, Alan; Trott, James (2002). Design Patterns Explained.
  • Humble, Jez; Farley, David (2010). Continuous delivery : reliable software releases through build, test, and deployment automation.
  • Martin, Robert (2008). Clean Code: A Handbook of Agile Software Craftsmanship.
  • Evans, Eric (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software.
  • Beck, Kent (2002). Test Driven Development. By Example.