Dec.10

Clean Architecture

Clean architecture divide software in different layers like onion and defines definition of dependency rule among those layers e.g. source code dependencies can only point inwards.

Clean architecture in general have four layers, but we can have less or more layers if necessary as long as we follow dependency rule strictly. 😃

Dependency Rule: Outer layers are mechanism and inner layers are policies. Inner layers can not know anything about outer layers e.g. anything declared in outer layers can not be mentioned in inner layers including variables, functions, classes or other entities. This rule is simple and says that source code dependencies can only point inwards.

Checkout: 😃 Photo Album 😍 (SOLID, MVVM, CLEAN)

Photo Album show usages of dependency rule. We may need more layers based on complexities and sizes of projects, but as long as we follow dependency rule, projects fall under clean architecture.

EntitiesCore Libraries: Entities encapsulate most general and high-level rules. Entities represent classes, functions, etc which are used by other outer layers. Operational or changes in outer layers should not effect entities. Examples: ServiceManager.

Use CasesModels: Use cases contain application specific business rules and orchestrate flow of data to and from entities, and direct those entities to use their rules to achieve goals of those use cases. Examples: AlbumListModel.

GatewaysViewModels: Gateways convert data from format most convenient for the use cases and entities, to format most convenient for some external agency such user interfaces. Examples: AlbumListActivityModel.

UIViews: UI mainly contain views, frameworks and tools. Generally we don’t write much code in this layer other than glue code that communicates to the next circle inwards. Examples: AlbumListActivity.

Remember: We can have as much layers as necessary but we have to follow dependency rule. Clean architecture is nothing but following dependency rule strictly. 🙂

Architecture,Principle

Dec.06

SOLID in OOP

SOLID is an acronym for 5 design principles in OOP (Object-Oriented Programming).

These principles are a subset of many principles introduced by Robert C. Martin aka Uncle Bob in Design Principles and Design Patterns. SOLID acronym was later introduced by Michael Feathers. 🙂

Objective of these principles is to make software design and development more understandable, maintainable and extendable.

Codes or examples are written in Kotlin language. So let’s start. 😃

S: Single Responsibility Principle
Every entities (classes, modules, functions, etc.) should only have a single responsibility.

Wrong

class Zoo(val database: Database) {
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         Log.e("Error", exception.message)
      }
   }
}

Right

class Zoo(val database: Database) {
   val logger: Logger = Logger()
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

Explanation: Zoo should not handle exceptions. So we are passing Exception to Logger for handling and allowing those to have single responsibility.

O: Open-Closed Principle
Every entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Wrong

class Zoo(val database: Database) {
   val logger: Logger = Logger()
   fun addAnimal(animal: Animal) {
      try {
         if (animal.type = "Giraffe") {
            database.addGiraffe(animal)
         } else {
            database.addAnimal(animal)
         }
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

Right

open class Zoo(open val database: Database) {
   val logger: Logger = Logger()
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

class GiraffeZoo(override val database: Database) : Zoo(database) {
   val logger: Logger = Logger()
   override fun addAnimal(animal: Animal) {
      try {
         database.addGiraffe(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

Explanation: By using inheritance e.g. override, we are adding extended behavior to Zoo without modifying existing method. Code is now clean and readable.

L: Liskov Substitution Principle
Entities should be replaceable with instances of their subtypes without altering correctness. Subtype should represents usage of base type.

Wrong

open class Zoo(open val database: Database) {
   val logger: Logger = Logger()
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

class PandaZoo(override val database: Database, val notifier: Notifier) : Zoo(database) {
   override fun addAnimal(animal: Animal) {
      notifier.notify(animal)
   }
}

Right

class PandaZoo(override val database: Database, val notifier: Notifier) : Zoo(database) {
   override fun addAnimal(animal: Animal) {
      super.addAnimal(animal)
      notifier.notify(animal)
   }
}

Explanation: By not invoking super or adding to database, we are altering functionality which doesn’t align with base class. PandaZoo is a subtype of Zoo, so it should extend without costing existing behavior.

I: Interface Segregation Principle
Many entity specific interfaces are better than one general purpose interface. No entity should be forced to depend on methods it does not use.

Wrong

interface IAnimal {
   fun awake()
   fun sleep()
   fun run()
   fun fly()
}

Right

interface IAnimal {
   fun awake()
   fun sleep()
}

interface IGirrafe : IAnimal {
   fun run()
}

interface IEagle : IAnimal {
   fun fly()
}

Explanation: Giraffes don’t fly and Eagles don’t run. Now we have entity specific interfaces and entities are not forced to depend on useless methods.

D: Dependency Inversion Principle
Dependency inversion principle is a specific way of decoupling entities. Higher entities should not depend on lower entities. Both entities should depend on abstractions.

Wrong

class Zoo(val database: Database) {
   val logger: Logger = Logger()
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

Right

class Zoo(val database: IDatabase, val logger: ILogger) {
   fun addAnimal(animal: Animal) {
      try {
         database.addAnimal(animal)
      } catch (exception: Exception) {
         logger.log(exception)
      }
   }
}

Explanation: Zoo should not create and depend on Logger directly. Plus we might want to use a subtype of Logger. So we are injecting dependencies (via interfaces) from outside instead of creating inside.

May be we are using SOLID in OOP all along more or less. 🤔 May be we didn’t know how to call it. But now we know! 😃

Architecture,Principle