Objects and Classes By albro

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@albro·
0.000 HBD
Objects and Classes By albro
<center>![Objects and Classes](https://files.peakd.com/file/peakd-hive/albro/EoibW8i1TMdR9esizdnfWcaXQYZd4KtLKMZkXywytusqxEj8Y6QV8bkTQAmgLbtvBpn.jpg)</center>

<p>This post is about classes and objects and I will talk about things like keeping classes clean and working with them cleanly. Also, before starting the discussion, we should understand the difference between real objects and class-like data structures. In the next step, I will go to the rules of working with object-oriented programming and we will get to know the rules of SOLID and demeter. Finally, I'll move on to polymorphism, except this time I'll focus specifically on classes.</p>
<p>Naturally, in this post, we are not going to teach object-oriented programming, and you are expected to know how to work with classes. Our goal is to write human-readable code.</p>
<h3>Difference between objects and data structures</h3>
<p>One of the first fundamental concepts for writing clean code in object-oriented programming is to understand the difference between real objects and data structures.</p>
<p>A real object hides its internal content (such as properties) and only allows us to access them using a public API (multiple methods). We do not mean only <code>getter</code> and <code>setter</code> methods by these few methods or public API, but all the methods that do a real job are part of this section. On the other hand, data structures, which are sometimes called data containers ) are also known as simple objects that display their content in a public way and we have almost no public API or methods to work with them.</p>
<p>So if you're using object-oriented programming, real objects are very important and usually contain the core logic of your program. On the other hand, there is no special logic in data structures, but we only use them to maintain and store data temporarily so that we can pass the data we want to different parts of the program. Also, when talking about real objects, there is a concept called abstraction over concretion. This concept means that we prefer abstraction over concreteness. what does it mean? That is, in real objects that are made of classes, we have methods, and these methods perform certain operations, and we do not care how these operations are performed (this is an abstract concept), if there are almost no methods in data structures. they don't have, so the data is available to us in concrete or "explicit" form and we work directly with this data.</p>
<p>For example, consider the following class:</p>
<pre><code>class Database {
  private uri: string;
  private provider: any;
  private connection: any;<br /><br /><br />
  constructor(uri: string, provider: any) {
    this.uri = uri;
    this.provider = provider;
  }<br /><br /><br />
  connect() {
    try {
      this.connection = this.provider.establishConnection(this.uri);
    } catch (error) {
      throw new Error('Could not connect!');
    }
  }<br /><br /><br />
  disconnect() {
    this.connection.close();
  }
}</code></pre>
<p>This is a class for a database and by using it we can create a real object because the object created from this class has its content hidden inside itself and then by using several methods it allows us to perform operations. gives a special For example, the <code>connect</code> and <code>disconnect</code> methods are high-level methods and perform important tasks, but when we work with an object made from this class, we do not know what is going on behind the scenes and how exactly these methods perform their tasks. but we only know that the <code>connect</code> method connects us to a database. This problem is called abstraction.</p>
<p>On the other hand, we have objects and classes that are basically data containers:</p>
<pre><code>class UserCredentials {
  public email: string;
  public password: string;
}</code></pre>
<p>As you can see, there is no method at all and to work with it, we have to work directly with its data such as <code>email</code> and <code>password</code>.</p>
<p>You may ask why this difference is important? If you consider these two types of objects as one, you will usually write codes that will not be clean and standard. Whether your entire program is written in an object-oriented way or whether it is a combination of procedural and object-oriented programming, you should still pay attention to this issue. For example, consider the <code>Database</code> class that I showed you above. If we want to use this class, our work will be easy and as follows:</p>
<pre><code>const database = new Database('my-database:8100', sqlEngine);
database.connect();<br /><br /><br />
database.disconnect();</code></pre>
<p>The code is very clean and readable by anyone. Also, whenever there is a need to change the connect and disconnect logic, we simply go back to the class definition and edit its corresponding methods, and we will not need to edit the above code (calling <code>connect</code> and <code>disconnect</code>). Now, if we don't want to make a difference between real objects and data structures, we may write the properties of the <code>Database</code> class as <code>public</code>:</p>
<pre><code>class Database {
  private uri: string;
  private provider: any;
  public connection: any;<br /><br /><br />
  constructor(uri: string, provider: any) {
    this.uri = uri;
    this.provider = provider;
  }
// Other Codes
</code></pre>
<p>For example, I have made the <code>connection</code> property (our connection to the database) <code>public</code>. In this case, the <code>connection</code> property is also available outside the class. For example, we may want to close the connection to the database in another part of the program as follows:</p>
<pre><code>const database = new Database('my-database:8100', sqlEngine);
database.connect();<br /><br /><br />
database.connection.close();</code></pre>
<p>As you can see, I have directly accessed the <code>connection</code> from the <code>database</code> object and called <code>close</code> on it. The problem is that in the first mode, which was the standard mode, we performed our operations by calling the methods, and when we needed to edit, we only had to edit the class definition, but what about this case? If we are going to directly access the content of the object like the code above, we will encounter many problems.</p>
<p>For example, if later the name of <code>close</code> is changed to something else like <code>shutDown</code> or <code>disconnect</code> or any other name, all our code will be broken and not only we have to edit the class definition but also we have to go to each part where we have closed the connection and then Edit it. Naturally, this work is not standard at all! In addition, <code>database.connection.close</code> is not readable. A person who wants to use our code must suddenly learn what <code>connection</code> is and where it comes from, and why we didn't have it when connecting (the <code>connect</code> method) and dozens of other questions that will confuse other developers.</p>
<p>This is why it is so important to understand the difference between real objects and data containers. We cannot treat both in the same way.</p>
<h3>Polymorphism in classes</h3>
<p>Polymorphism means methods or objects that have a fixed shape and name in appearance (for example, they are called in the same way), but in practice, based on how you use them, it behaves completely differently to the point where it seems as if we have used another method or object.</p>
<p>The problem here is that polymorphism is usually active in the field of classes and objects and is less explained in the field of methods. For this reason, in this section, I want to pay special attention to polymorphism in classes. First, I have prepared a simple class for you:</p>
<pre><code>type Purchase = any;<br /><br /><br />
let Logistics: any;<br /><br /><br />
class Delivery {
  private purchase: Purchase;<br /><br /><br />
  constructor(purchase: Purchase) {
    this.purchase = purchase;
  }<br /><br /><br />
  deliverProduct() {
    if (this.purchase.deliveryType === 'express') {
      Logistics.issueExpressDelivery(this.purchase.product);
    } else if (this.purchase.deliveryType === 'insured') {
      Logistics.issueInsuredDelivery(this.purchase.product);
    } else {
      Logistics.issueStandardDelivery(this.purchase.product);
    }
  }<br /><br /><br />
  trackProduct() {
    if (this.purchase.deliveryType === 'express') {
      Logistics.trackExpressDelivery(this.purchase.product);
    } else if (this.purchase.deliveryType === 'insured') {
      Logistics.trackInsuredDelivery(this.purchase.product);
    } else {
      Logistics.trackStandardDelivery(this.purchase.product);
    }
  }
}</code></pre>
<p>This is a class called <code>delivery</code>, which has a property called <code>purchase</code>, has a <code>constructor</code>, and finally has two methods, the first one (<code>deliverProduct</code>) is responsible for sending the product based on the type of delivery, and the second one (<code>trackProduct</code>) is responsible for tracking the sent product. We have three types of shipping for our products: normal shipping (called in the <code>else</code> section), express or fast shipping, and insured shipping.</p>
<p>If we want to use polymorphism in this example, instead of one class, we should divide our classes into several different classes so that each class is specifically responsible for doing a part of the sending process. For example, we will have a base class for delivery, which has shared logic between all shipping methods, and then we will have a separate class for each specific type of shipping and tracking products:</p>
<pre><code>class ExpressDelivery {}<br /><br /><br />
class InsuredDelivery {}<br /><br /><br />
class StandardDelivery {}</code></pre>
<p>Why did we do this? If you look at the initial code, you will notice that the method of sending the goods plays an important role in this code and everything is based on the shipping methods, so dividing the code based on the shipping method will be an ideal option. Now, for each class, we must have a product sending method and a product tracking method. For example, for the <code>express</code> class, we say:</p>
<pre><code>class ExpressDelivery {
  deliverProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}</code></pre>
<p>The problem is that currently the <code>ExpressDelivery</code> class is an independent class and there is no longer a feature called <code>purchase</code> in it, and of course we do not have access to it. To solve this problem, we must use <strong>inheritance</strong>. It means that the <code>ExpressDelivery</code> class is a child of the <code>Delivery</code> class (basic and main class). If you look at the <code>Delivery</code> class, you can see that the <code>purchase</code> attribute is <code>private</code>, which means it is only available in the <code>Delivery</code> class itself. I change it to <code>protected</code> so that it can be accessed in child classes as well:</p>
<pre><code>class Delivery {
  protected purchase: Purchase;<br /><br /><br />
  constructor(purchase: Purchase) {
    this.purchase = purchase;
  }
// Other codes</code></pre>
<p>In the next step, we <code>extend</code> the <code>ExpressDelivery</code> class:</p>
<pre><code>class ExpressDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueExpressDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}</code></pre>
<p>As you can see, we have two methods in this class, the first one (<code>deliverProduct</code>) to send the product and the second one (<code>trackProduct</code>) to track it. No more if conditions. All that's left is to repeat this for the other two classes:</p>
<pre><code>class ExpressDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueExpressDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}<br /><br /><br />
class InsuredDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueInsuredDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackInsuredDelivery(this.purchase.product);
  }
}<br /><br /><br />
class StandardDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueStandardDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackStandardDelivery(this.purchase.product);
  }
}</code></pre>
<p>Now that we have three special classes for sending and tracking all kinds of goods, we have to go to the basic class (<code>Delivery</code>) and delete both of its methods because we don't need them anymore. With this account, all the codes of this file will be as follows:</p>
<pre><code>type Purchase = any;<br /><br /><br />
let Logistics: any;<br /><br /><br />
class Delivery {
  protected purchase: Purchase;<br /><br /><br />
  constructor(purchase: Purchase) {
    this.purchase = purchase;
  }
}<br /><br /><br />
class ExpressDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueExpressDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}<br /><br /><br />
class InsuredDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueInsuredDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackInsuredDelivery(this.purchase.product);
  }
}<br /><br /><br />
class StandardDelivery extends Delivery {
  deliverProduct() {
    Logistics.issueStandardDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackStandardDelivery(this.purchase.product);
  }
}</code></pre>
<p>You are probably asking, so what are the if conditions? How to determine which class we should instantiate and call its methods? You should check the sending method in the part of the program where you want to use these classes. We are defining these classes here, and naturally, in real programs, the place of defining and using the classes are separate.</p>
<p>For example, suppose we want to use these delivery classes in a part of the program, naturally we have to create an example of them and work with that object. Before making the above changes, we used to make changes as follows:</p>
<pre><code>const delivery = new Delivery({});
delivery.deliverProduct();</code></pre>
<p>Currently, it is not possible to write such a code because there is no longer a method named <code>deliverProduct</code> in the main class of <code>Delivery</code>. How you use these classes is highly dependent on your program and your files and your personal decisions, but if I want to give you a simple example I'll use this example:</p>
<pre><code>let delivery: Delivery;<br /><br /><br />
if (purchase.deliveryType === 'express') {
  delivery = new ExpressDelivery(purchase);
} else if (purchase.deliveryType === 'insured') {
  delivery = new InsuredDelivery(purchase);
} else {
  delivery = new StandardDelivery(purchase);
}<br /><br /><br />
delivery.deliverProduct();</code></pre>
<p>In this example, based on the type of user's purchase (<code>Express</code> or <code>insured</code> or <code>Standard</code>), we set the value of the <code>delivery</code> variable. In addition, these codes are written in Typescript language, so don't be surprised by the presence of two dots to determine the type of <code>delivery</code> variable at the beginning of the codes. These are minor issues and have nothing to do with clean code. Finally, I have called the <code>delivery</code> method. Although this simple example should show you the generality of the work, but for more practice, let's rewrite all the codes of this file in a clean way, assuming that we want to use these classes in this same file.</p>
<p>In the example above, you will get an error that the <code>deliverProduct</code> method does not exist at all on the <code>delivery</code> object, which is correct. My goal in rewriting is to clear these errors. To solve this problem, we can use TypeScript interfaces. Example:</p>
<pre><code>interface Delivery {
  deliverProduct();
  trackProduct();
}</code></pre>
<p>I have defined the general structure of <code>Delivery</code> using this <code>interface</code>. In the next step, I change the name of the <code>Delivery</code> class to <code>DeliveryImplementation</code> (meaning the implementation of Delivery, which is our interface) so that the naming is more accurate:</p>
<pre><code>class DeliveryImplementation {
  protected purchase: Purchase;<br /><br /><br />
  constructor(purchase: Purchase) {
    this.purchase = purchase;
  }
}</code></pre>
<p>In the next step, we must <code>extend</code> this new class (we have changed the name of the parent class, so the codes will be confused) and then use the interface for child classes with the keyword <code>implement</code>:</p>
<pre><code>class ExpressDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueExpressDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}<br /><br /><br />
class InsuredDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueInsuredDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackInsuredDelivery(this.purchase.product);
  }
}<br /><br /><br />
class StandardDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueStandardDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackStandardDelivery(this.purchase.product);
  }
}</code></pre>
<p>At the end, you can use the same <code>if</code> conditions that I showed, but usually in real programs, these conditions are used several times, so it is better to make it in the form of a separate function (a factory function):</p>
<pre><code>function createDelivery(purchase) {
  if (purchase.deliveryType === 'express') {
    delivery = new ExpressDelivery(purchase);
  } else if (purchase.deliveryType === 'insured') {
    delivery = new InsuredDelivery(purchase);
  } else {
    delivery = new StandardDelivery(purchase);
  }
  return delivery;
}<br /><br /><br />
let delivery: Delivery = createDelivery({});<br /><br /><br />
delivery.deliverProduct();</code></pre>
<p>This function receives the user's purchase and then, based on the type of purchase, calls the correct delivery class and returns it in the form of the <code>delivery</code> variable. Next, we can call the <code>deliverProduct</code> method according to our taste and in the right place. I've passed an empty object to <code>createDelivery</code> instead of an actual order because our code isn't real and we don't have a real purchase to pass, but you won't have this problem in your real apps. With this account, all our codes are as follows:</p>
<pre><code>type Purchase = any;<br /><br /><br />
let Logistics: any;<br /><br /><br />
interface Delivery {
  deliverProduct();
  trackProduct();
}<br /><br /><br />
class DeliveryImplementation {
  protected purchase: Purchase;<br /><br /><br />
  constructor(purchase: Purchase) {
    this.purchase = purchase;
  }
}<br /><br /><br />
class ExpressDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueExpressDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackExpressDelivery(this.purchase.product);
  }
}<br /><br /><br />
class InsuredDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueInsuredDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackInsuredDelivery(this.purchase.product);
  }
}<br /><br /><br />
class StandardDelivery extends DeliveryImplementation implements Delivery {
  deliverProduct() {
    Logistics.issueStandardDelivery(this.purchase.product);
  }<br /><br /><br />
  trackProduct() {
    Logistics.trackStandardDelivery(this.purchase.product);
  }
}<br /><br /><br />
function createDelivery(purchase) {
  if (purchase.deliveryType === 'express') {
    delivery = new ExpressDelivery(purchase);
  } else if (purchase.deliveryType === 'insured') {
    delivery = new InsuredDelivery(purchase);
  } else {
    delivery = new StandardDelivery(purchase);
  }
  return delivery;
}<br /><br /><br />
let delivery: Delivery = createDelivery({});<br /><br /><br />
delivery.deliverProduct();</code></pre>
<p>[hive: @albro]</p>
👍 , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,