Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 16, 2021 10:55 am GMT

Object Design Style Guide Summary

How you should create and use your objects

Im currently reading an interesting book called Object Design Style Guide, wrote by Matthias Noback, about how to create objects as best as possible, so I decided to bring it up here and show you some tips and directives I found more interesting. Of course, I recommend you read the whole book if you want to go deep into this topic.
image

1 Introduction to some OOP concepts

In this book, inheritance plays a small role, even though its supposed to be one of the pillars of object-oriented programming. In practice, using inheritance mostly leads to a confusing design.
In this book, well use inheritance mainly in two situations:

  • When defining interfaces for dependencies (dependency injection and inversion).
  • When defining a hierarchy of objects, such as when defining custom exceptions that extend from built-in exception classes.In most other cases wed want to actively prevent developers to extend from our classes. You can do so by adding the final keyword in front of the class. It will be more explained later.The composition is highly recommended over the inheritance.

A little space to the test about that the basic structure of the unit test is:
The basic structure of each test method is Arrange - Act - Assert:
1 Arrange: Bring the object that were testing (also known as the SUT, or Subject Under Test) into a certain known state.
2 Act: Call one of its methods.
3 Assert: Make some assertions about the end state.

Shit's getting real. There are 2 types of objects:

  • Service objects that either perform a task or return a piece of information. Objects of the first type will be created once, and then be used any number of times, but nothing can be changed about them. They have a very simple lifecycle. Once theyve been created, they can run forever, like little machines with specific tasks. These kinds of services are immutables.Service objects are do-ers, and they often have names indicating what they do: renderer, calculator, repository, dispatcher, etc.
  • Objects that will hold some data, and optionally expose some behavior for manipulating or retrieving that data, this kind is used by the first type to complete their tasks. These objects are the materials that the services work with. There are two subtypes: value objects and models/entities, but don't get ahead of ourselves.

2 Focus on Services

There is a pile of suggestions about how a service should be, Ill make the list hiper-summarizing them:

  • To create a service use dependency injection to make the service ready for use immediately after instantiation and test double. So the dependencies should be declared explicitly.Here is an example of this, you can see how the parameter $appConfig is only used to get the directory of the cache so Instead of injecting the whole configuration object, make sure you only inject the values that the service actually needs.
interface Logger{    public function log(string $message): void;}final class FileLogger implements Logger{    private Formatter $formatter;    // Formatter is a dependency of FileLogger    public function __ construct (Formatter $formatter)     {        $this->formatter = $formatter;    }    public function log(string $message): void    {        formattedMessage = $this->formatter->format ($message) ;        // .    }}
  • When possible you should keep together all the related configuration values that belong together. Service shouldnt get the entire global configuration object injected, only the values that it needs.

WRONG WAY

final class MySQLTableGateway{    public function __construct (        string $host,        int $port,        string $username,        string $password,        string $database,        string $table    ) {        // ...    }}

GOOD WAY

final class MySQLTableGateway{    public function __construct(        ConnectionConfiguration $connectionConfiguration,        string $table    ) {        // $table is the name of the table, It isnt necessary to make the connection     }}
  • Avoid service locators (a service from which you can retrieve other services) and inject the dependencies that you need explicitly.
  • All the constructor arguments should be required because the code will be unnecessarily complicated. If you have the temptation to put it as an optional dependency you can use the null object.
  • Services should be immutable, that is, impossible to change after they have been fully instantiated because the behavior could be so unpredictable.

So... avoid something like this:

final class EventDispatcher{    private array $listeners = [];    public function addListener (        string $event,        callable $listener    ): void {        $this->listeners[event][] = $listener;    }}
  • Only assign properties or throw exceptions because of some validation error in the constructor.
final class FileLogger implements Logger{    private string $logFilePath;    public function __construct (string $logFilePath)    {        // $logFilePath should be properly set up, so we just need a safety check        if (! is_writable($logFilePath)) {            throw new InvalidArgumentException(                'Log file path  . $logFilePath .  should be writable            );        }        $this->logFilePath = $logFilePath;    }    public function log(string message): void    {        // ...    }}
  • Ideally, create objects to avoid the hidden dependencies, for example, the function json_encode() or a class from PHP like DateTime

WRONG WAY

final class ResponseFactory{    public function createApiResponse(array $data): Response    {        // json_encode is a hidden dependency        return new Response (        json_encode(            $data,            JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), ['Content-Type' => 'application/json']        );    }}

GOOD WAY

final class JsonEncoder{    / **    * throws RuntimeException    */    public function encode (array $data): string    {        try {            return json_encode (                $data,                JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT            );        // we can throw our own exception, with more specific info        ) catch (RuntimeException previous) (            throw new RuntimeException(                'Failed to encode data: ' . var_export($data, true),                0,                previous            );        }    }}final class ResponseFactory{    private JsonEncoder $jsonEncoder;    // JsonEncoder can be injected as a dependency    public function __construct (JsonEncoder $jsonEncoder)    {        $this->jsonEncoder = $jsonEncoder;    }    public function createApiResponse(data): Response    {        return new Response ($this->jsonEncoder->encode($data));    }}

And you can do the same with the date() and big core utilities of your language, and your application layer will be so decoupled.

3 other objects

3.1 value object and model/entity

The main suggestions are:

  • Validate the objects in the constructor, it will assure you that you only have valid objects in your application, every object will be what it was intended to be. You should throw exceptions in the constructor in case the data is not valid. The book suggests avoiding using custom exceptions for invalid argument exceptions, for this kind of RuntimeExceptions indicates that more about it below.
final class Coordinates{    public function _construct (float $latitude, float $longitude)    {        if ($latitude > 90 || $latitude < -90) {            throw new InvalidArgumentException (                'Latitude should be between -90 and 90'            );        }        $this->latitude = $latitude;    }}
  • Don't use property fillers, later well see where you can use them with an example.
  • Entity/model should be identifiable with a unique id, value objects dont because they only wrap one or more primitive-type values.
  • To add more semantic to a constructor the named constructors appear, those are static methods with domain-specific names that allow your code to have a better name than the typical new class().

Standard way

$salesOrder = new SalesOrder();

Better way

$salesOrder = SalesOrder::place();

You can put the __construct method as private to avoid using it and call the constructor inside the place() method.

final class DecimalValue{    private int value;    private int precision;    private function __construct(int  $value, int $precision)    {        this.value = value;        Assertion.greaterOrEqualThan ($precision, 0);        $this->precision = $precision;    }    public static function fromInt (        int $value,        int $precision    ): DecimalValue {        return new DecimalValue($value, $precision);    }    public static function fromFloat (        float $value,        int $precision    ): DecimalValue (        return new DecimalValue(            (int) round($value * pow(10, precision)),            $precision        ):    }}
  • One of the best points of the value objects is that, if you validate in their constructor, when you see a value object you will know that it contains validated information and you will not have to validate this information in other points of the code.
  • Test the behavior of an object and the constructor in the way it will fail, don't create a test just to check if the values are correct.
public function it_can_be_constructed(): void{    $coordinates = new Coordinates(60.0, 100.0);    assertEquals(60.0, $coordinates->latitude());    assertEquals(100.0, $coordinates->longitude());}
  • In summary, a value object does not only represent domain concepts. They can occur anywhere in the application. A value object is an immutable object that wraps primitive-type values.

3.2 DTO (Data transfer object)

The rules of 3.1 don't fit well with this type of object, the DTO. While in the value object and model we want consistency and validity of the data, in the DTO we just want (nobody expected it by the name) to transfer data from one point to another.

  • A DTO can be created using a regular constructor.
  • Its properties can be set one by one.
  • All of its properties are exposed, so make them public and access them directly without getters.
  • Its properties contain only primitive-type values.
  • Properties can optionally contain other DTOs or simple arrays of DTOs.
  • You can use property fillers when needed.
final class ScheduleMeetup{    public string $title;    public string $date;    public static function fromFormData l(        array $formData): ScheduleMeetup {    $scheduleMeetup = new ScheduleMeetupl();    $scheduleMeetup->title = $formData['title'];    $scheduleMeetup->date = $formData['date'];    return $scheduleMeetup;}

Well, the objects have methods where they contain the behavior, there are two kinds of methods, queries to retrieve information and commands to perform a task, but both of them could be designed with the same template, that is:
1 Checking parameters, throwing errors if there is something wrong.
2 Do what the method has to do, throwing errors if necessary.
3 Check postcondition checks. This wont be necessary if you have good tests, but for example, if you are working with legacy code it could be good for safety checks.
4 Returns if is a query method.

Youve seen that the exceptions are a good part of a code, some cases where using a custom exception is very useful:
1 If you want to catch a specific exception type higher up

try {    // possibly throws SomeSpecific exception} catch (SomeSpecific $exception) {    // }

2 If there are multiple ways to instantiate a single type of exception

final class CouldNotDeliverOrder extends RuntimeException{    public static function itWasAlreadyDelivered(): CouldNotDeliverOrder    {        // ...    }    public static function insufficientQuantitiesInStock(): CouldNotDeliverOrder    {         //...    }}

3 If you want to use named constructors for instantiating the exception

final class CouldNotFindProduct extends RuntimeException{    public static function withId(        ProductId $productld    ): CouldNotFindProduct (        return new CouldNotFindProduct ('Could not find a product with ID . $productld);    }}throw CouldNotFindProduct .withId(/* ... */);

And you dont have to put Exception in the name of the exception class, instead, use explicit names like InvalidEmailAddress or CouldNotFindProduct.

And thats all folks, there are soooo many more examples in the book, so I encourage you to take a look at it. If you want the second part of the book, let me know in the comments.

Sources and more info


Original Link: https://dev.to/migueldevelopez/object-design-style-guide-summary-42bl

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To