Extremely Serious

Category: Programming (Page 4 of 5)

SOLID Principles

Introduction

In the ever-evolving world of software development, creating maintainable and scalable code is crucial. The SOLID principles offer a set of guidelines to achieve just that. First introduced by Robert C. Martin, these five principles provide a foundation for writing clean, flexible, and efficient code. In this article, we will delve into each SOLID principle, understand its significance, and explore how they contribute to building robust and maintainable software.

Single Responsibility Principle (SRP)

The Single Responsibility Principle advocates that a class should have only one reason to change. In other words, it should have a single responsibility and encapsulate a single functionality. By adhering to SRP, we can avoid coupling and improve maintainability. This principle encourages us to decompose complex functionalities into smaller, independent classes, making our code easier to understand, test, and modify.

Example

// Bad example: A class with multiple responsibilities
class Order {
    public void calculateTotalPrice() {
        // Calculation logic here
    }

    public void saveToDatabase() {
        // Database insertion logic here
    }

    public void sendConfirmationEmail() {
        // Email sending logic here
    }
}

// Good example: Separating responsibilities into different classes
class OrderCalculator {
    public void calculateTotalPrice() {
        // Calculation logic here
    }
}

class OrderRepository {
    public void saveToDatabase() {
        // Database insertion logic here
    }
}

class EmailService {
    public void sendConfirmationEmail() {
        // Email sending logic here
    }
}

Open/Closed Principle (OCP)

The Open/Closed Principle suggests that software entities (classes, modules, functions) should be open for extension but closed for modification. This means that we should design our code in a way that new functionalities can be added without altering existing code. This promotes code reuse and allows us to adapt to changing requirements without affecting the stability of the existing system.

// Bad example: A class that needs to be modified to add new shapes
class Shape {
    public void draw() {
        // Drawing logic for the shape
    }
}

// Good example: Using an abstract class or interface to support new shapes
interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        // Drawing logic for a circle
    }
}

class Rectangle implements Shape {
    public void draw() {
        // Drawing logic for a rectangle
    }
}

The Factory Pattern is a design pattern that aligns well with the Open/Closed Principle (OCP) by allowing you to create new objects without modifying existing code.

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle emphasizes that objects of a superclass should be replaceable with objects of its subclasses without altering the correctness of the program. In simpler terms, derived classes should adhere to the contract established by their base class. This principle ensures that polymorphism works as expected, promoting code flexibility and reliability.

// Bad example: Square is a subclass of Rectangle but violates LSP
class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

// Good example: Avoiding LSP violation by not using inheritance
class Shape {
    public int getArea() {
        return 0;
    }
}

class Rectangle extends Shape {
    protected int width;
    protected int height;

    // constructor, getters, and setters
}

class Square extends Shape {
    protected int side;

    // constructor, getters, and setters
}

Interface Segregation Principle (ISP)

The Interface Segregation Principle suggests that a class should not be forced to implement interfaces it does not use. Instead of having a single large interface, we should create multiple smaller interfaces, each representing a specific set of related methods. This allows clients to depend on the minimal set of methods they require, reducing the risk of coupling and providing a more coherent system.

// Bad example: A large interface containing unrelated methods
interface Worker {
    void work();

    void eat();

    void sleep();
}

// Good example: Splitting the interface into smaller, cohesive ones
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

interface Sleepable {
    void sleep();
}

class Robot implements Workable {
    public void work() {
        // Robot working logic
    }
}

class Human implements Workable, Eatable, Sleepable {
    public void work() {
        // Human working logic
    }

    public void eat() {
        // Human eating logic
    }

    public void sleep() {
        // Human sleeping logic
    }
}

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle focuses on decoupling high-level modules from low-level modules by introducing abstractions and relying on these abstractions. High-level modules should not depend on low-level modules directly; instead, they should depend on interfaces or abstract classes. This promotes flexibility, ease of testing, and modularity, as changes in low-level modules won't affect the higher-level ones.

// Bad example: High-level module depends on low-level module directly
class OrderService {
    private DatabaseRepository repository;

    public OrderService() {
        this.repository = new DatabaseRepository();
    }

    // OrderService logic using DatabaseRepository
}

// Good example: Using abstractions to invert the dependency
interface Repository {
    void saveData();
}

class DatabaseRepository implements Repository {
    public void saveData() {
        // Database saving logic
    }
}

class OrderService {
    private Repository repository;

    public OrderService(Repository repository) {
        this.repository = repository;
    }

    // OrderService logic using Repository
}

High-Level Module

A high-level module is a component or module that deals with broader, more abstract, and higher-level functionality of a software system. It often represents a larger part of the application and is responsible for orchestrating the interactions between various low-level modules. High-level modules tend to focus on business logic, overall system behavior, and user interactions.

Low-Level Module

A low-level module is a more specialized and granular component that handles specific, detailed, and focused functionality within a software system. These modules are typically closer to the underlying hardware or foundational operations of the system. They encapsulate specific operations or algorithms and are designed to perform a specific task or handle a specific aspect of the application.

Conclusion

The SOLID principles serve as a compass to guide software developers towards writing cleaner, more maintainable, and robust code. By adhering to these principles, developers can create flexible and scalable software systems that are easier to understand, modify, and extend. Embracing SOLID principles fosters good coding practices, promotes teamwork, and contributes to the long-term success of software projects. As you embark on your software development journey, keep these principles in mind, and witness the positive impact they bring to your projects. Happy coding!

Regex Capture Groups with Java

The following java code extracts the group, artifact and version using regex capture groups:

import java.util.regex.Pattern;

public class Main {

    public static void main(String ... args) {
        //Text to extract the group, artifact and version
        var text = "org.junit.jupiter:junit-jupiter-api:5.7.0";

        //Regex capture groups for Group:Artifact:Version
        var pattern = "(.*):(.*):(.*)"; 

        var compiledPattern = Pattern.compile(pattern);
        var matcher = compiledPattern.matcher(text);
        if (matcher.find( )) {
            System.out.println("Whole text: " + matcher.group(0) );
            System.out.println("Group: " + matcher.group(1) );
            System.out.println("Artifact: " + matcher.group(2) );
            System.out.println("Version: " + matcher.group(3) );
        } else {
            System.out.println("NO MATCH");
        }
    }
}

Output

Whole text: org.junit.jupiter:junit-jupiter-api:5.7.0
Group: org.junit.jupiter
Artifact: junit-jupiter-api
Version: 5.7.0

Retrieving the Versions from maven-metadata.xml

Groovy Snippet

List<String> getMavenVersions(String metadataXmlURL) {
    def strVersions = new ArrayList<String>()
    def mvnData = new URL(metadataXmlURL)
    def mvnCN = mvnData.openConnection()
    mvnCN.requestMethod = 'GET'

    if (mvnCN.responseCode==200) {
        def rawResponse = mvnCN.inputStream.text
        def versionMatcher = rawResponse =~ '<version>(.*)</version>'
        while(versionMatcher.find()) {
            for (nVersion in versionMatcher) {
                strVersions.add(nVersion[1]);
            }
        }
    }

    strVersions.sort {v1, v2 ->
        v2.compareTo(v1)
    }

    return strVersions
}

Example Usage

def metatdataAddress = 'https://repo.maven.apache.org/maven2/xyz/ronella/casual/trivial-chunk/maven-metadata.xml'
def versions = getMavenVersions(metatdataAddress)
println versions

Functional Programming with Gosu

First Class Citizen

An entity that can be passed around as an argument, returned from a function, modified and assigned to a variable.

First Class Function

A function that is treated as first class citizen.

Higher-Order Functions (HOF)

A function which takes function as an argument and/or that return a function.

Closure

A function that remembers its lexical scope even when the function is executed outside that lexical scope.

function greeterFn() : block() {
  var name="World" //name is a local variable created by init
  var greet = \-> print("Hello ${name}") //greet is an inner function 
                                         //that uses the variable declared 
                                         //in parent function.
  return greet
}

var greeter = greeterFn()
greeter()

Currying

The process of converting a function that takes multiple arguments into a function that takes them one at a time.

var sum = \ a : int, b : int -> a + b
print(sum(1,2))

var curriedSum = \ a : int -> \ b : int -> a + b
print(curriedSum(1)(2))

Function Composition

The act of putting two functions together to form a third function where the output of one function is the input of the other.

uses java.lang.Integer
uses java.lang.Double
uses java.lang.Math

var compose = \ output : block(out : Integer) : String, func : block(param: Double) : Integer -> \ arg : Double -> func(output(arg)) //Definition

var floorToString = compose(\ out -> out.toString(), \ param -> Math.floor(param)) //Usage

print(floorToString(121.212121))

Continuation

The part of the code that's yet to be executed.

uses java.lang.Double

var printAsString = \ num : Double -> print("Given ${num}")

var addOneAndContinue = \ num: Double, cc : block(___num : Double) -> {
  var result = num + 1
  cc(result)
}

addOneAndContinue(2, printAsString)

Purity

A function is pure if the return value is only determined by its input values, and does not produce side effects.

var greet = \ name: String -> print("Hello ${name}")
greet("World")

The following is not pure since it modifies state outside of the function:

var greeting : String
var greet = \ name: String -> {greeting ="Hello ${name}"}
greet("World")
print(greeting)

Side Effects

A function or expression is said to have a side if apart from returning a value, it interacts with (reads from or writes to) external mutable state.

var currentDate = java.util.Date.CurrentDate //Retrieves the date from the system.
print(currentDate)

gw.api.util.Logger.forCategory("side-effect").info('IO is a side effect!')

Idempotent

A function is idempotent if reapplying it to its result does not produce a different result.

print(java.lang.Math.abs(java.lang.Math.abs(10)))

Point-Free Style (Tacit Programming)

Write functions where the definition does not explicitly identify the arguments used. This style usually requires currying or other higher order functions.

uses java.lang.Integer

// Given
var map = \ fn : block(item : int) : int -> \ list : List<Integer> -> list.map<Integer>(\ item -> fn(item))
var add = \ a : int -> \ b : int -> a + b
var nums : List<Integer> = (0..5).toList()

// Not points-free - 'numbers' is an explicit argument
var incrementAll = \ numbers : List<Integer> -> map(add(1))(numbers)

print(incrementAll(nums))

// Points-free - The list is an implicit argument
var incrementAll2 = map(add(1))

print(incrementAll2(nums))

Predicate

A function that returns true or false for a given value.

var predicate = \ a : int -> a > 2

print((1..4).where(\ a -> predicate(a)))

Lambda

An anonymous function that can be treated like a value.

(\ a : int -> a + 1)(1)

Lambda can be assigned to a variable

var add1 = \ a : int -> a + 1
print(add1(1))

Reference

https://github.com/hemanth/functional-programming-jargon
https://en.wiktionary.org/wiki/second-class_object#English
https://en.wiktionary.org/wiki/third-class_object#English

Gosu Collection Enhancements Functions

flatMap

Maps each element of the Collection to a Collection of values and then flattens them into a single List.

Syntax

flatMap<R>(mapper(item : Collection<?>) : Collection<R>) : List<R>

Example

var items = {{1},{2,2},{3,3,3},{4,4,4,4}}
print(items.flatMap(\ ___item -> ___item))
Output
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

fold

Accumulates the values of an Collection into a single T.

Syntax

fold(aggregator(aggregate : T, item : T) : T) : T

Example

var items = {{1},{2,2},{3,3,3},{4,4,4,4}}
var flatItems = items.flatMap(\ ___item -> ___item)
print(flatItems.fold(\ ___aggr, ___item -> ___aggr + ___item))
Output
30

intersect

Returns a Set that is the intersection of the two Collection objects.

Syntax

intersect(that: Collection<T>) : Set<T>

Example

var items1 = {1,2,3,4,5,6}
var items2 = {4,5,6,7,8,9}
print(items1.intersect(items2))
Output
[4, 5, 6]

map

Returns a List of each element of the Collection mapped to a new value.

Syntax

map<Q>(mapper(item : <INPUT>) : <Q>) : List<Q>

Example

var numbers = {1,2,3}
var words = {"one","two","three"}
print(numbers.map(\ ___number -> words[___number-1]))
Output
[one, two, three]

partition

Partitions this Collection into a Map of keys to a list of elements in this Collection.

Syntax

partition<Q>(partitioner(item : R) : Q) : Map<Q, List<R>>

Example

var numbers = {1,2,3,4,5,6,8}
print(numbers.partition<String>(\ ___number -> ___number % 2 == 0 ? "even" : "odd"))
Output
{even=[2, 4, 6, 8], odd=[1, 3, 5]}

reduce

Accumulates the values of a Collection into a single V given an initial seed value

Syntax

reduce<T>(init : T, aggregrator(aggregate: T, item : ?) : T) : T

Example

var numbers = {1,2,3,4,5,6,8}
var sumOfNumbers = numbers.reduce(0, \ ___aggr, ___number -> ___aggr + ___number)
print(sumOfNumbers)
Output
29

union

Returns a new Set that is the union of the two Collections

Syntax

union(that : Collection<T>) : Set<T>

Example

var items1 = {1,2,3,4,5,6}
var items2 = {4,5,6,7,8,9}
print(items1.union(items2))
Output
[1, 2, 3, 4, 5, 6, 7, 8, 9]

disjunction

Returns a new Set that is the set disjunction of this collection and the other collection

Syntax

disjunction(that : Collection<T>) : Set<T>

Example

var items1 = {1,2,3,4,5,6}
var items2 = {4,5,6,7,8,9}
print(items1.disjunction(items2))
Output
[1, 2, 3, 7, 8, 9]

join

Joins all elements together as a string with a delimiter

Syntax

join(delim : String) : String

Example

var words = {"Hello", "World"}
print(words.join(" "))
Output
Hello World

Covariant vs Contravariant

Covariant

The type can vary in the direction as the subclass.

Contravariant

The reverse of covariant.

Example in Generics

Language Covariant Contravariant
C# IList<out T> IList<in T>
Java List<? extends Number> List<? super Integer>

Testable Codes

  • Favor composition over inheritance.
  • Block of codes must not be more than 60 lines.
  • Cyclomatic complexity of 1 to 10 as long as you can still write unit tests.
  • Idempotent implementation.

Parallel Extensions

This extension calculates the most efficient way of dividing the task to the different cores available.

Note: Parallel extensions will block the calling thread.

Method Description
Parallel.For Executes for that may run in parallel.
Parallel.ForEach Executes foreach that may run in parallel.
Parallel.Invoke Executes actions that may run in parallel.

Asynchronous Programming Keywords

Async modifier

A modifier that indicates that the method can be run asynchornously.

Await keyword

Wait for a result of the asynchronous operation once the data is available without blocking the current thread. It also validates the success of the asynchronous operation. If everything is good, the execution will continue after the await keywords on its original thread.

Task.Run method

Run a task on a separate thread

ContinueWith Method

A task method that will be invoked after the running async task has returned a result.
The parameter TaskContinuationOption of this method can be use to control if the continue must be invoked if there are no exception or only with exception.

CancellationTokenSource

CancellationTokenSource is used to signal that you want to cancel an operation that utilize the token. This token be passed as a second parameter for the Task.Run method.

Dispatcher.Invoke() in WPF

Communicate with the thread that owns our UI.

ConcurrentBag Class

A thread safe collection.

Cyclomatic Complexity

Cyclomatic complexity is the count of linearly independent paths in a program's source code. With the help of control flow graph, we can use the following formula to calculate this:

CC = E - N + 2P

Variable Description
E The number of edges in the control flow graph.
N The number of nodes in the control flow graph.
P The number of connected components. This has a value of 1 if you are computing at the method (i.e. function or subroutine) level.

Related
Control Flow Graph

« Older posts Newer posts »