Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, John Vlissides, Ralph Johnson and Richard Helm is one of those books that’s mentioned everywhere as “one book every developer should read”, then I’ve started reading it and decided to share my thoughts as it helps me to absorb the information. The first chapter is a nice review on object oriented programming, here we go!

Introduction

What is a design pattern? The book says “Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice”, thus, design patterns make it easier to reuse successful designs and architectures. Design patterns cover architectural problems that are definitely not trivial to solve, and they’re categorized in two criterias:

  • Purpose:
    • Creational patterns concern the process of object creation.
    • Structural patterns deal with the composition of classes or objects.
    • Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.
  • Scope:
    • Class patterns deal with relationships between classes and their subclasses. These relationships are established through inheritance, so they are static—fixed at compile-time.
    • Object patterns deal with object relationships, which can be changed at run-time and are more dynamic. Almost all patterns use inheritance to some extent. So the only patterns labeled “class patterns” are those that focus on class relationships. Note that most patterns are in the Object scope.”

Object Granularity

Design patterns help to determine the object granularity, i. e, size and number of objects. They also help to decide what should be an object, for example, you can use the Facade pattern to produce a higher level of abstraction for a complex system (I intend to detail specific patterns on next posts).

Object Interfaces

The set of all signatures defined by an object’s operations is called the interface to the object. An object’s interface characterizes the complete set of requests that can be sent to the object.” A type denotes a particular interface, that said, an object might have several types - as long as it implements multiple interfaces - and different objects might share the same type - part of an object may characterize one type and other part another type.

A nice thing about interfaces is that they don’t specify implementations, each class is free to implement an interface on its own way. Different objects might support the same request with completely different implementations. “The run-time association of a request to an object and one of its operations is known as dynamic binding.

Dynamic binding means that issuing a request doesn’t commit you to a particular implementation until run-time. Consequently, you can write programs that expect an object with a particular interface, knowing that any object that has the correct interface will accept the request.” The ability to trust the same request to different objects (that implement the same interface) is known as polymorphism.

Object Implementations (AKA Classes)

An object’s implementation is defined by its class, but some classes might prefer to defer - some or all of its - implementations to subclasses, those classes are known as abstract classes and since they defer implementation(s), you can’t instantiate an object from an abstract class. Classes that are not abstract are known as concrete classes and inheritance may be used to override (redefine) the parent’s class implementations.

Class vs Interface

An object’s class defines how the object is implemented. The class defines the object’s internal state and the implementation of its operations. In contrast, an object’s type only refers to its interface — the set of requests to which it can respond.” In other words, type only refers to the interface and class refers to the implementation and internal state. So there’s a conceptual difference between class inheritance and interface inheritance (or subtyping):

  • Class inheritance defines an object’s implementation in terms of another object’s implementation: it’s a mechanism for code and representation sharing.
  • Interface inheritance (or subtyping) describes when an object can be used in place of another.”

Class inheritance is basically just a mechanism for extending an application’s functionality by reusing functionality in parent classes. It lets you define a new kind of object rapidly in terms of an old one. It lets you get new implementations almost for free, inheriting most of what you need from existing classes.” The author adds that the proper way of using inheritance is when “all classes derived from an abstract class will share its interface”, implying that “_ a subclass merely adds or overrides operations and does not hide operations of the parent class_”, that is, if you’re trying to reduce a parent’s functionality in a subclass you probably shouldn’t be using inheritance. Why? Because requests should be able to trust on an object’s interface, that’s how polymorphism works. There are some benefits of manipulating objects in terms of their interfaces:

  • The system that asks requests to an interface is unaware of the specific types of objects they use, as long as the objects adhere to the interface it expects. Which makes easier to change behavior just by swaping objects that implement the same interface.
  • The system remain unaware of the classes that implement these objects. Systems only know about the abstract class(es) defining the interface. Thus, reducing coupling since the system can trust multiple classes that implement a given interface.

These qualities lead us to the following principle of reusable object-oriented design:

Program to an interface, not an implementation.

Reuse mechanisms: Inheritance vs Composition

Inheritance and composition are two common reuse mechanisms in object-oriented systems. Reuse by inheritance is also known as white box reuse, refering to the fact that internals of a parent class are visible to subclasses. On the other hand, reuse by composition is achieved by composing objects with well defined interfaces to achieve a complex functionality (wikipedia has a good example), it’s known as black box reuse due to the fact that object’s internal details are not visible.

There are some disadvantages of inheritance, for example, “you can’t change the implementations inherited from parent classes at run-time, because inheritance is defined at compile-time. Second, and generally worse, parent classes often define at least part of their subclasses’ physical representation… any change in the parent’s implementation will force the subclass to change.”, i. e., if the parent class doesn’t fit the problem domain exactly, you’ll need a new parent class. The author suggests to inherit from abstract classes whenever is possible, since they don’t provide implementations, the problem won’t happen.

Object composition also solves the previous problem, since it trusts on the interface, it’s decoupled from implementations allowing objects to be dynamically defined at run-time. And since no implementations are exposed, encapsulations is never broken, leading us to the second principle of object-oriented design:

Favor object composition over class inheritance.

You shouldn’t need new components to achieve reuse, some component should already provide the necessary functionality for your system. But since that’s rarely the case, inheritance and composition work together.

It’s good to keep in mind that composition isn’t perfect, using it in order to achieve more flexibility also yields highly parameterized software, possible computational inefficiencies due to multiple indirections and code that’s hard to understand.

Parameterized types (AKA generics, AKA templates)

Parameterized types are another way of achieving reuse on typed languages. You probably already seen some template <typename T> class Something out there on C++ wilderness, that’s how you say that the type of T will be determined by whoever uses Something, like in Something<int>, that’s how containers such as std::vector are implemented.

Toolkits and Frameworks

They provide another way of achieving reuse, their main difference is that a toolkit is basically a library, for example, you might use a specific math lib throughout your system and it won’t affect your system’s architecture, but using a framework would. A framework comes with several design decisions already implemented, thus taking away some code design complexity from the hands of the developer. Some good examples of frameworks are game engines such as MonoGame, Heaps, Phaser, and some Python frameworks such as Django and Pytest.

If applications are hard to design, and toolkits are harder, then frameworks are hardest of all.

Keep in mind that using frameworks makes your application to rely on them, thus the application needs to evolve together with its frameworks.

Conclusion

The book covers a lot more ground than is written here, but these are the parts I consider good to highlight. I’m enjoying it so far, I wasn’t expecting such a good object-oriented programming review and principles on its first chapter. I intend to share my thoughts on the next chapters as well.

How it started

Some months ago I started playing around with Godot engine, I’ve started with a Pong clone just to learn the basics, then - when talking about it to a friend - came the idea of this game where the player reflects projectiles with some sort of magic shield. And here I am, making some good progress (considering the short amount of free time I have) thanks to how high level development Godot provides, its awesome community, great documentation and great learning references, such as GD Quest!

The dash skill and its trail effect

The problem:

I have this modular character composed by head and body sprites, it doesn’t have multiple animation frames, its animations are just position and scale tweens.

My solution:

I’ve created a script and attached it to a child node of my character. This dash script has a owner_path that should point to the character. It assumes the character has some methods:

# Character.gd
func get_speed():
  return _speed

func set_speed(value):
  _speed = value

func set_input_enabled(enabled):
  _input_enabled = enabled

func get_current_sprites():
  var head = get_node("YSort/Node2D/Head")
  var body = get_node("YSort/Node2D/Body")
  return [head, body] 

Then it uses those methods to achieve the dash with trail effect:

# Dash.gd
extends Node

# The owner of this Dash node needs to have the following methods:
# func set_input_enabled(enabled:bool)
# func get_speed()
# func set_speed(speed_scalar)
# func get_current_sprites() -> List[Sprite]

export (NodePath) var owner_path
var owner_body
var owner_speed

# list of list: each list contains all necessary sprites to represent a 
# ghost copy of the original character
var ghosts = [] 
var tween

func _ready():
    owner_body = get_node(owner_path)
    var num_sprites = owner_body.get_current_sprites().size()
    for i in range(5): # 5 ghost trails
        var sprites = []
        for j in range(num_sprites): 
            var s = Sprite.new()
            s.set_visible(false)
            add_child(s) 
            sprites.append(s)
        ghosts.append(sprites)

    tween = Tween.new()
    add_child(tween)

func dash():
    owner_body.set_input_enabled(false)
    owner_speed = owner_body.get_speed()
    owner_body.set_speed(owner_speed * 3)

    var timer = Timer.new()
    timer.connect("timeout", self, "_return_input_to_owner") 
    add_child(timer) 
    _start_ghost_tweens()
    timer.start(0.25)
    timer.set_one_shot(true)

func _start_ghost_tweens():
    for i in range(ghosts.size()): # ghost trails
        yield(get_tree().create_timer(0.05), "timeout")

        for j in range(ghosts[i].size()): # ghost parts (head, body, etc)    
            var owner_sprites = owner_body.get_current_sprites() 
            var ghost_part = ghosts[i][j]

            ghost_part.set_scale(owner_body.global_scale)
            ghost_part.set_position(owner_sprites[j].global_position)
            ghost_part.set_texture(owner_sprites[j].get_texture())
            ghost_part.set_rotation(owner_sprites[j].global_rotation)
            ghost_part.flip_h = owner_sprites[j].flip_h
            ghost_part.set_visible(true)

            tween.interpolate_property(
                ghost_part, 
                "modulate", 
                Color(1, 1, 1, 1), 
                Color(1, 1, 1, 0), 
                0.25, 
                Tween.TRANS_LINEAR, 
                Tween.EASE_IN
            )
            if not tween.is_connected("tween_completed", self, "_on_complete_ghost_tween"):
                tween.connect("tween_completed", self, "_on_complete_ghost_tween") 
            tween.start()

func _on_complete_ghost_tween(object, key):
    object.set_visible(false)

func _return_input_to_owner():
    owner_body.set_speed(owner_speed)
    owner_body.set_input_enabled(true)

Next

You can easily change this script to export the number of ghost trails and tween variables so you can change them directly on the editor. Also, it might be a good idea to detach (by creating another script) the trail from the dash logic, after all you might want to add the trail effect to other things that won’t have a dash skill.

Thanks for reading!

You must gather your party aliases before venturing forth

First things first, let’s make our lives easier by adding some aliases to git, you can either run these commands

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

Or you can run git config --global -e to open your text editor, and add the following to your config file:

[alias]
  co = checkout
  br = branch
  ci = commit 
  st = status

Now, instead of typing git status you can simply type git st. Consider the amount of key presses you’re going to save.

Sample config file in case you need one

The following config file contains the autocrlf option to properly configure the line-endings, and the option that stores your credentials so you don’t need to login everytime you want to push something to your remote.

[user]
  name = Your name goes here
  email = your_email@youknow.com
[core]
  autocrlf = true
[credential]
  helper = store
[alias]
  co = checkout
  br = branch
  ci = commit 
  st = status

Cheat sheet

# Create branch and change to it: 
git co -b <branch_name>

# Show untracked files: 
git clean -dn

# Delete untracked files: 
git clean -df

# Cherry-pick (it's like merging one commit in your branch): 
git cherry-pick <commit_hash>

# Overwrite the last commit (very useful to change your commit message or
# details in your last commit): 
git commit --amend

#After running amend, you'll need to run:
git push --force

# Delete local branch: 
git branch -d <local_branch>

# Delete branch from the remote:
git push origin --delete <remote_branch>

Bonus

If you’re on Windows, consider installing Cmder, it’s much better than the Windows command prompt.