First 250 days of software dev - Part 7
Day 61
I really enjoyed the work I did today. I don't know how much I did right, how much better I could have done, but I created most of what I needed to do using foreachs. In the morning, I completed the missing javascript code. I realized that we wrote long code in 2-3 days (thanks to HTML and CSS, of course). When I first saw such long codes on your computer, I was amazed, I wondered how you could write such long codes and still read them. Now, at least with HTML and CSS, almost all of it can be written and readable (at least the ones we made).I want to end the day with this lyric:
“Work it harder, make it betterDo it faster, makes us stronger
More than ever, hour after hour
Work is never over”
Day 62
At the beginning of the day I found a resource on how to write a chess game in OOP. I wanted to convert the code written in Java to PHP and then use HTML and CSS to show it on a web page. I thought it would be practical for OOP, but it seemed to take a long time and I didn't want to be idle, so I worked on it. Then I looked at the code you sent me. I tried to understand the structures used. I thought a lot about why and where it can be used. I couldn't think of much about its purpose, but at least I read the code. I tried to interpret it. I can say that I spent the whole day with this. I encountered the ArrayObject structure for the first time (I had encountered it before but I didn't know what it meant) and I tried to understand it directly from the document. It was difficult to understand or try to understand this code. And since there is no improvement without difficulty, I tried as much as I could. That's all for today.Day 63
Yesterday, I only reviewed inheritance, abstract class, and interface topics. I tried to make them fit if there was anything that didn't fit. I didn't discover anything that didn't fit theoretically, I tried to add something on top of it. I did a lot of reading on it. Interface is there to define the mandatory methods that other developers must have in the classes they will produce when they will work in the system you produce. Abstract class, on the other hand, is and should be used to make the developers who will work in your system use some abstract methods and provide them with some base methods when they create their child classes. Both have their pros and cons. The reasons for preference will also vary depending on what kind of structure will be built. There is no such thing as you should definitely use interface or abstract. It may vary according to the project and the requirements of the system. As we know, every class that will implement the interface must use every method in the interface. They can be implemented by classes more than once. They can also be used by completely different and unrelated classes. If you ask where inheritance is in this, the answer can be this: Interface provides only functional inheritance.There's a perspective on Inheritance that I hear a lot:
Abstract class establishes an "is a" relationship with concrete classes. This applies not only to abstract classes but also to any "base class-child class" relationship. Interface provides the "has a" capability for classes.
Interface is basically a contract, a contract. The person who writes the interface says, "Dude, I accept things this way". And the person who uses it says, "OK, I assure you that the classes I will write will be like this". So the interface is like an empty shell. It only specifies what the methods will be. It is not full. They can't do anything on their own. They are just a pattern. Interfaces consume very little CPU. Because they are not a class. At the point where this becomes important, they can be taken into consideration in issues such as embedded software.
Abstract classes are a class unlike an interface. They are similar to interfaces, of course, but there are a few differences. For example, you can define a behavior for them. In other words, methods can be filled in or you can define variable-property in them. Abstract classes are more like "These classes should look like this, and they may have things in common. They can also use the same method differently. I'll tell you the method they will use. You fill it in the way you want.
Day 64
Today was a review. I looked again at the topics we have seen before. I reviewed the relationships between objects, the concept of abstaction, polymorphism, inheritance and many other topics related to OOP. On the OOP side, I asked you and google about the points I was missing on the topics I looked at today. I was able to find answers to the questions that remained in my mind. Tomorrow I will write in general terms what I understand in more detail. I took notes of many details that I understood and did not understand during the day. Tomorrow I will continue with design patterns from where I left off.Day 65
I would like to add some of the notes I took today and yesterday to my report today. Here's what I want to write in summary:- Some of the purposes of using OOP: Reduce complexity, reduce maintenance cost, increase modularity, establish hierarchy between objects.
- OOP is based on four basic principles: Encapsulation, abstraction, inheritance, polymorphism.
-
Relationships between objects: Inheritance, implementation, association, dependency, composition, aggregation. Simple encoding of these structures:
- Inheritance: is+a (A is a B),
- Implementation: should + do (A should do B),
- Dependency: references (A references B),
- Composition: has + a, wholepart, ownership,
- Aggregation: has + a, wholepart.
- Dependency
- An example for both aggregation and composition
- One last example of this
- Association: I have an association with an object. "Foo uses Bar".
- Composition: I own an object and I am responsible for its lifetime. "When Foo dies, so does Bar".
- Aggregation: I own an object that I borrowed from someone else. "When Foo dies, Bar may live on".
- A different example from a different subject. About Abstraction
- SOLID
- Single Responsibility Principle
Each class, method, function should have a single responsibility. If you give more than one responsibility to a class, there will be problems in the future when you make a change in the code. This will increase the cost. Clean code says: “There should never be more than one reason for a class to change”. - Open Closed Principle
“Software entities should be open for extension, but closed for modification”.So you have to allow users to add new functionality without changing the existing code.
- Liskov Substitute Principle
Since subclasses derive from superclasses, they inherit their behavior. If they don't perform the behavior of their superclasses, we probably leave the method that does the behavior empty or throw an error. However, this leads to code pollution and invalid code. In addition, it also creates problems for developers who will be involved in the project later. The developer may try to use a behavior that is not implemented, thinking that the system is working fine.
- Interface Segregation Principle
Together with the property that a class can implement more than one interface, this principle says that in such cases interfaces should be separated and used as needed.
“Clients should not be forced to depend upon interfaces that they do not use”.
- Dependency Inversion Principle
Design Patterns
"Each pattern describes a problem that occurs over and over again in our environment, and then describes the application of a solution to that problem in such a way that you can use that solution a million times without doing it the same way twice." - Christopher Alexander
“Design patterns are solutions to recurring problems; guidelines on how to tackle certain problems”.
"Keep in mind! Design patterns are solutions to problems. They are not a solution to finding problems. They are a savior if you use them in the right way in the right place, otherwise they can cause terrible consequences in your code."
Creational Design Patterns
It deals with how to create an object or a group of objects in a flexible and reusable way. Types: Singleton, Simple factory, factory method, factory method, abstract factory, builder, prototype.- Singleton
Useful if only one and only one object is required to coordinate actions throughout the system.
Conctructor must be private. The purpose of this is to ensure object creation in one place, to prevent the client from using the "new" keyword. It must be a static variable, to hold the reference. There must be a method to access the held reference, getInstance().
In multithreaded applications, multiple threads can create different objects at the same time. There are structures in the current language that prevent this (such as lock, synchronized).
- Disadvantages of Singleton
- The biggest disadvantage is one of its raison d'être: it is global. It also exhibits anti-pattern behavior. Why is it bad to use it as a global instance? You store the dependencies of the application in your code instead of exposing them with an interface. Making something global causes code smell (I haven't investigated code smell much yet).
- It goes against the principle of single responsibility. Because they control their own creation and their own life cycle.
- They make the code hereditarily tightly coupled (tightly coupled).
- It has been said in some examples that an interface can be used as a solution to tightly coupling, and that the structure of the singleton class can be changed and used in test problems. In this way, the problematic parts of singleton can be solved. But you need to be very careful when using it (if you don't know how to use it in the right place in the right way).
- They retain their status for the lifetime of the application. This is again a bad thing for the unit test. Because each test should be independent of the other. That is, it should be used in such a way that it cannot affect the behavior of the application.
The aphorism on this subject is:
A short break while continuing with Creational Design patterns: Prefer composition over inheritance. Why should we prefer composition over inheritance whenever we can?“When you think you need a global, you’re probably making a terrible design misktake”.
- You cannot change the implementation inherited from superclasses in runtime (because inheritance is defined in compiled time).
- Inheritance exposes the sublass' elaboration of the implementation of the parent class. This is why inheritance is said to break encapsulation.
- The tight coupling provided by Inheritance tightly connects the implementation of the subclass to the implementation of the parent. So any change in the parent's implementation will push and force the subclass to change.
- Overuse of subclass can make the inheritance stack too deep and confusing.
- On the other hand, objects reference other objects, while object composition is defined at runtime. In this case, these objects will not have access to the protected data of other objects (no encapsulation break) and will respect the interface of each of them.
- A Bar is a Foo (if Inheritance establishes the relation "is + a" and has nothing to do with "has + a"),
- Bar can do everything Foo can do (if you can reuse the code in the base classes),
- If you want to make global changes to derived classes by changing the base class.
- Simple Factory
- Factory Method
- Abstract Factory
- Builder
Day 66
- Simple Factory
In plain words:
In short, it is an object used to create other objects.“Simple factory simply generates an instance for client without exposing any instantiation logic to the client”.
When to use it? If creating an object is not just a few simple operations and involves some logic, it makes sense to write a "dedicated factory" during the creation phase, instead of repeating code everywhere.
- Factory Method
For example, let's say we have a logistic app. With this app we only support land logistics. If we want to include sea transportation in our app, there is a problem! The existing classes in our code are tightly coupled with each other. What can you do to solve such a situation? Factory method is used here and can provide a solution.
When should it be used? You can use it if you don't already know the dependencies and exact types of the objects for the code to work. It can also be used to provide users with a way to extend the internal components of our library and framework. It can also be used to conserve system resources by reusing existing object2s instead of always rebuilding them.
In addition, the factory method separates product production from where the product is actually used. This allows us to extend the product generation side independently of the rest of the code. For example, to add a new product to your app, just create a new creator subclass and override the factory method.
Pros: Tight coupling between creator and concrete products is avoided. Fully complies with SRP and OCP if written correctly.
Let's give another example of this issue. Let's say that the user who wants to create/log in using their social media accounts to log in or become a member of our site can log in with Facebook. Let our system be fully compatible with this. But if users now want to log in with their linkedin account and we want to make other additions in the future, we provide this possibility with the factory method here. We make our code more flexible.
Working principles of factory method ingredients:
- Our Creator class defines the factory method and returns the object of the product class. Creator's subclasses usually implement this method.
- The creator can also provide the default implementation of the factory method.
- Creator's main obligation is not to create a product. It contains the basic business logic related to the product object returned by the method. Subclasses can indirectly change the business logic by overriding the method and return a different product type.
- Concrete creators overwrite the factory method to change the product type.
- Product interfaces define the operations that all concrete products must follow.
- Concrete products provide implementations of the product interface.
- All these things I've written seem pointless when I read them afterwards because they don't make sense without a sample code. But abstract factory and factory method are the parts that I had the hardest time in these three days, so I took a lot of notes, so I'll try to write the important parts here.
- Abstract Factory
In cases where we have to work with multiple product families, Abstract Factory is the right approach to abstract the client from these structures.
Let's go back to our door example in the simple factory. Depending on your needs, you buy a wooden door from a carpenter, an iron door from a blacksmith, a PVC door from a plastic shop. You also need to find a craftsman to install each of them separately. Because they all have different craftsmen. To create a wooden door, you need wood and a master. The same is true for the others. We use Abstract Factory to bring two different objects together to create a door.
When do we use it? You can use it if your code needs to work with different families of the product, but you don't want to depend on concrete classes of those products (they may not be known in advance and you want to allow future extensibility).
Let's take a different example: You can provide an infrastructure for creating different types of templates for different elements of a web page. A web application can support different rendering engines at the same time only if its classes are independent of the rendering engine's concrete classes. Therefore, you have to communicate with the app's objects only using their abstract interfaces. Your code should not directly create template objects, but assign special factory objects for their creation. Finally, your code should not depend on factory objects, but should instead work with them through the abstract factory interface. As a result, you will be able to provide the app with the factory object corresponding to one of the rendering engines. Every template created in the app will be created by the factory, and their type will match the factory's type. If you want to change the rendering engine, you will be able to pass a new factory to the client code without breaking the existing code.
In addition to different sources, it can be said:
Wherever you need runtime values to create a particular dependency, you can use Abstract factory.
As I researched real-life examples of abstract factory, I found that it is frequently used in Dependency Injection issues. Here are some examples of where it is actually used:“ The abstract factory pattern provides a way to encapsulate a group of indivual factories that have a common theme without specifying their concrete classes.” - Wikipedia
- You need to supply one or more parameters only known at runtime before you can resolve a dependency,
- The lifetime of the dependency is conceptually shorter than the lifetime of the consumer.
Differences between Factory method and Abstract Method:
Abstract factory creates base classes that contain abstract methods for the objects that need to be created. Each factory class derived from the base class can create its own implementation of any object type.
Factory method is a simple method used to create objects in a class.
In summary; the main purpose of the class containing the Factory method is not to create objects. Abstract Factory should only be used to create objects. Another difference is that abstract factory is implemented by composition, but factory method is implemented by inheritance. The most important point to understand is that abstract factory is injected into the client. That's why we say there is composition.
Note: You should be careful when using factory methods, as it is easy to do something against the LSP when creating objects.
- Builder
Let's say we went to Burger King. We asked for a double menu and they brought it. This can be likened to a simple factory. But there is also the possibility that the creation logic involves more steps. For example, "How do you want your burger? Is the meat rare? What toppings do you want us to add? Do you want pickles on it? " and so on. This is where the builder pattern comes to the rescue.
In plain words:
If our constructor has many parameters, it is very likely to get confused. The more parameters, the harder it is to organize its content. This is called telescoping constructor anti-pattern in the literature. The only alternative to this is to apply the builder pattern.“It’s a design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern”. - Wikipedia
The main difference between factory pattern and builder method is that factory pattern is used if the production is a one-step process and builder method is used if the production consists of many steps.".
If, against the telescoping constructor pattern, you create the instance in the cilent and call each method one by one, an inconsistent situation may occur at any time of the construction because the object is created over several calls. This can cause extra effort to ensure thread safety. So the best alternative is builder pattern.“The builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters”. - Joschua Bloch
Benefits:
- Parameter values are all in one location.
- Makes code easier to write, read, and understand.
- The build method can be modified to control parameters and throw an "IllegalStateException" if an invalid parameter value is given.
- It is flexible. It is much easier to add parameters in the future.
- Prototype
In PHP, cloning can be done directly with the "__clone" magic method. Or you can do it directly with "clone".
When to use it? If you need a duplicate of an existing object or if creation is more costly than cloning. To produce objects that cost less to produce (by cost you can mean parameterized constructors etc.). There are two types: Shallow copy and deep copy. Shallow copy copies the addresses of objects in memory. Since it is a superficial operation, no new object is created. Deep copy is what we want. We can copy the object exactly, and this copy and the original object can be marked with different references.
Amaç: "bir nesnenin oluşturulma süresini azaltmayı amaçlamaktadır.
Structural Design Patterns
It focuses on how objects can be joined to each other.- Adapter
In plain words:
Let's take an example from real life: Let's imagine that a system needs an interface for external DVRs. Every DVR manufacturer provides a library that allows us to write code to control their devices. Let's call this library an SDK. Even though each SDK has interfaces that provide the most basic functionality of DVRs, they are not all the same. In other words, it may differ from SDK to SDK. The software we will write should interact with all DVRs. Instead of opening a separate switch case for each different SDK, we can create a common interface and develop all our system code through this interface. A different adapter can be written for each different SDK and implement our interface. This way we can make our code simpler, thanks to the Adapter pattern.“Adapter pattern allows the interface of an existing class to be used as another interface. Often used to make existing classes work with others without modifying their source code”.
A simpler example is: Our system uses the JsonSerializer interface for JSON operations. But let's say we want to add something 3rd party and use a different interface. Since our system runs on JsonSerializer, we use the adapter pattern to make the 3rd party software work by looking like JsonSerializer.
- Bridge
Example: Let's imagine we have a website with different pages. Imagine that users can change the theme of the pages (like dark mode). What would you do? Make a copy of each page and create a theme for all of them? Or just create a separate theme that will be loaded according to the user's preference? Bridge pattern allows us to do the latter.
In plain words:
"Bridge pattern, 'prefer composition over inheritance. Implementation details are pushed away from the main hierarchy and into a separate hierarchy of another object'.“Decouple an abstraction from its implementation so that the two can vary independently”. - Wikipedia
Since it's late, I'll pick up where I left off tomorrow. Since I'm writing directly from the notebook, some parts may have gotten too long and complicated. I was aware of it while I was writing, but the place that didn't fit the best so far, or the place that I thought I would have difficulty if I came across it, was Abstract Factory. Many of the places that I had difficulty with until now were settled after I worked on them again and again. I believe it will fit too.“Adapter makes things work after they’re designed; Bridge makes them work before they are”. - GOF (page 219)
Day 67
- Composite
In plain words:
"Composite patttern allows the client to treat individual objects in the same manner.""Defines grouping different objects and treating them as a single instance of an object." - Wikipedia
Purpose: Compose objects into a tree structure to represent a part-whole hierarchy. Applying the composite pattern allows the client to treat individual objects and compositons homogeneously.
Let's illustrate: Every organization, every company has employees. Each of the employees has the same characteristics. They have salaries. They have responsibilities. They may or may not have to report to someone. They may or may not have subordinates. We can put them in the same collection and collect their salaries if we want, or we can apply other methods for all of them.
"Use the composite pattern when You want to represent represent part-whole relationship; You want clients to be able to ignore th difference between compositions of objects and indivual objects. Clients will treat all objects in the composite structure uniformly." - GOF
- Decorator
"Allows adding behavior to a single object, either statically or dynamically, without affecting the behavior of other objects derived from the same class. Useful for adhering to the single responsibility principle." - Wikipedia
- Facade
In plain words:
A Design Pattern provides a common way of solving a recurring problem. The classes in Design Patterns are just normal classes. What matters is how they are structured and how they work together in the best way to solve a problem."Facade pattern provides a simplified interface to a complex system".
Facade design patterns simplify the interface of a complex system. It consists of all the classes that make up the subsystems of a complex system. Facade protects the user from the complex details of the system and provides a simplified view that is easy to use. It also separates the code that uses the system from the details of the subsystems, making it easier to modify the system later.
I have written about this before, but it is worth repeating for myself. The important thing when learning design patterns is to recognize which pattern fits our given problem and then use it appropriately. It is a big problem to use a pattern in the wrong way or to try to solve your problem only with it because you know a pattern. It is useful to learn design patterns before falling into these traps.
For Facade, in a nutshell, the goal is not to introduce a new infrastructure into the system, but to abstract the complexity of the subclasses and offer practicality.
- Flyweight
In plain words:
"It is used to minimize memory usage or computational expenses by sharing as much as possible with similar objects."
"Flyweight is an object that tries to minimize memory usage by sharing as much data as possible with other similar objects. It is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory." - WikipediaAs we said, the goal is to stop initializing unnecessary objects and take up less memory space. In GoF there are two states of an object:
"Intrinsic State: Stored in the Flyweight. It consists of information independent of the Flyweight content. This makes it shareable.Extrinic State: It can change with the content of the Flyweight and depends on it. Therefore it is not shareable. Client objects are responsible for passing the extrinic state to the flyweight when needed." ? "I don't quite understand this sentence.???
- Proxy
Although it is not used in most PHP applications, there are some areas where it is used: caching, logging, access control, delayed initialization. For example, downloader is an example of this. The proxy can be used to improve the performance of the downloader by using caching. For example, let's say you downloaded a file. Then you will download the same product again. Giving this data directly to you with caching in the first download saves both time and traffic.
A real-world example is the credit card. A credit card is a proxy for our bank account. It can be used instead of cash and allows us to withdraw cash when needed. This is exactly what a proxy does:
"Control and manage access to the object they are protecting".
"In its most general form, a proxy is a class that acts as an interface to something else. A proxy is a wrapper or agent object that is invoked by the client to access the real serving object behind the scenes. The use of a proxy can be a redirect to the real object or provide additional logic. Extra functionality can be provided in the proxy, such as caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked." - Wikipedia
Let's summarize the structural design patterns before moving on to the next topic. Let's write down the differences between them. Because all of them are structurally similar to each other and the possibility of getting confused is very high. Proxy, decorator, adapter, and bridge are all variations on "wrapping" a class. But they are used differently.
Proxy: Can be used if you want to lazy-instantiate an object, or hide that you are calling a remote service, or control access to the object.
Decorator: It is called "Smart Proxy". It can be used if you want to add new functionality to an object without extending its type. This allows you to do it at runtime.
Adapter: You can use it when you have an abstract interface and you want to map it to the interface of another object with a different interface but very similar functionality.
Bridge: It is very similar to Adapter, but you use it when you define both the abstract interface and the underlying implementation. For example, you are not adapting some 3rd party code and you are the designer and owner of all the code. And you just want to change some different implementations.
Facade: It is a high-level interface created for subclasses of one or more classes. Let's say you have a complex structure that requires multiple objects to be created. Making changes to this set of objects can be confusing because you don't always know which object has the method you want to call. It's time to write a facade with high-level methods for complex operations on your collection of objects!
Behavioral Design Patterns
It allows us to make common communication between objects effective and flexible.- Chain of Responsibility
In plain words:
"Helps to generate a chain of objects. The request starts from an object and travels through individual objects until the appropriate handler is found."Let's say I have 3 forms of payment. The first is my bank account, the second is my Paypal account, and the third is bitcoin. Let's say I have 100 liras in the bank, 200 liras in my Paypal account, and 300 liras in my bitcoin wallet. Now I'm going to shop for 259 liras. I say, "Dear bank account, I need to buy 259 liras from you". But we see that there is not that much there. And my bank account says, "I'll direct you to your Paypal account. I don't have that much change." When the Paypal account didn't have that much money, it redirected to the bitcoin wallet and finally the transaction was completed because there was money there. What was done here was about the behavior of things. It has a name like chain of obligation, and of course for objects. At least because we're moving towards OOP.
- Command
In plain words:
"It allows us to encapsulate actions in objects. The main idea behind this pattern is to provide ways to separate the client from the receiver."
"A behavioral design pattern in which an object is used to encapsulate all the information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method, and values for method parameters." - WikipediaLet's give an example. Imagine you place an order in a restaurant. You (Client) ask the waiter (Invoker) to bring you an iskender (Command). And the waiter passes your request to the chef (Receiver), who is the person who knows how to cook the iskender.) Another example: You (Client) turn on the television (Receiver) using the remote control (Invoker).
Today I switched to behavioral design patterns. Tomorrow I will finish it completely and repeat it all on Saturday.
Day 68
Introduction to UML
Introduction
UML is like looking at your feet when you walk. We all read, write, interpret code. Whether it's our hobby, our job, or passing a required programming language course. And a software developer is not usually inclined to interpret code or draw diagrams of it. It makes more sense to read the code directly. But there are some benefits that you might want to consider and use. It can be used for both forward engineering and reverse engineering.You should not think of UML only in relation to something you do. For example, you are in the forest at night and it starts to rain. It's pitch black. Even if you look at your own feet, it won't help because you can't see anything. But if someone was near with a torch, you could follow their light or their footprints. You would find your way through the mud, the darkness, the trees, the thorny grass. As an example of this analogy, a project is started in a company. After 5 years, what happens when the people who were involved in the project before are gone? It is incredibly useful to have some basic up-to-date documentation available for everyone who joins the project later. And here's the reality. If every part of a project is fully described with method and parameter names, it is difficult to maintain and sustain. Still, it is very valuable to show basic diagrams of the relationships between structures in the system. In general, no one wants to read a 50-page UML diagram with everything fully rendered. It is therefore necessary to create a UML diagram of a system in a measured way. Another useful point of UML is that a senior developer in the software department is responsible for designing a structure, leaving the implementation of the design to the junior developer.
In general, we have tried to explain what UML is used for, using non-unique analogies (based on stack overflow entries). Now let's look at what UML is.
Next Section
UML is a modeling language. Its main purpose is to visualize a designed system. UML is actually a visual language, not a program language. UML is related to object-oriented programming and provides the visualization of the relationship between elements with diagrams. These diagrams can be divided into two: Behavior diagram and Structural diagram.Class Diagrams
Since it would be extremely long and time consuming to explain UML in its entirety, let's go straight to class diagrams. Class diagrams are the main "building blocks" of every object-oriented method. They are used to show relationships between classes, the use of interfaces, and things related to other classes. Class diagrams consist of the following structures:- Class {name, attribute, method}
- Objects
- Interface
- Relationships {inheritance (generalization), implementation (realization), association, dependency, composition, aggregation}
- Associaitons {bidirectional, unidirectional}
There are 3 types of "modifiers" with which we can specify the view (public, private, protected) of methods and properties: "+" provides public visibility (for everyone). "#" provides protected visibility (so that it can also appear in derived classes). "-" provides private visibility (only for itself).