Wiki source code of Objects and Data Structures
Last modified by chrisby on 2024/03/03 17:05
Hide last authors
author | version | line-number | content |
---|---|---|---|
![]() |
4.4 | 1 | #### Motivation |
2 | |||
3 | Objects and data structures are completely different concepts and should not be mixed. No concept is inherently superior, but they provide distinct options for extensiblity whose usefulness depends on the requirements. | ||
4 | |||
5 | #### Definitions | ||
6 | |||
7 | * **Data structures** are a concept of an entity to provide direct access to their fields, but don't provide functions to manipulate that data. Simply put, it has public fields and no methods. Instead, external functions work on the data. | ||
8 | * **Objects** are entities that encapsulate data and behavior together. They hide their internal data from direct external access and instead provide functions for interacting with that data. Simply put, an object has private fields and at least one public method. | ||
9 | * **Avoid getters & setters in objects**: Private variables are designed to restrict external access. However, introducing getters and setters for each variable softens this principle by allowing indirect access. When information is required to be returned from an object, it is usually in a more abstract or processed form than the raw data fields. | ||
10 | |||
11 | #### Data Structure / Object Anti-Symmetry | ||
12 | |||
13 | * **"Easily extensible"** ideally means not touching existing code, but simply writing new code. | ||
14 | * **Data structures are easily extended with functionality.** | ||
15 | * **Objects are easily extended with data types.** | ||
16 | * Here is a [[code example|doc:Software Engineering.Clean Code.Objects and Data Structures.Code Example\: Data Structure Style vs Object Style .WebHome]] that describes the reasoning at a source code level. | ||
17 | * **The right tool for the job** - to put it in a different way: | ||
18 | * If the requirement is to easily extend functionality in the future, then the data structure style should be used. | ||
19 | * If the requirement is to easily add new objects in the future, then the object style should be used. | ||
20 | * The phrase "everything is an object" is a myth. Good programmers are aware of this fact and use the appropriate approach depending on the situation. | ||
21 | |||
22 | #### Law of Demeter | ||
23 | |||
24 | * **Definition**: A method f of a class C should only use the following: | ||
25 | * other methods of C. | ||
26 | * objects created by f. | ||
27 | * objects passed to f as arguments. | ||
28 | * objects contained as instance variables of C. | ||
29 | * **Motivation - Minimize Coupling**: This law minimizes coupling between components in a software system, ensuring that objects communicate only with close "neighbors/friends" and not with distant objects. This reduces dependencies and the impact of changes throughout the system. | ||
30 | * **Speak to friends, not strangers**: | ||
31 | * **Definition of Friends and Strangers**: Let's say A is an object that is expected to use the methods of object B. A and B are friends. The same is true for B and C, so they are also friends. A and C are not expected to communicate directly, so they are strangers. Therefore, A's source code should not contain any source code references to C, only references to B. Instead, B should handle C internally and not reveal its relationship to C to A in any way. | ||
32 | * Modules should not know anything about the inner workings of the objects they manipulate. The inside should not be revealed by accessors. A manipulates B, but should not know anything about C. | ||
33 | * Don't use `friend.getStrangerObject().executeMethodOfStrangerObject()`. This is a common code smell where B would return C as an object to A. | ||
34 | * Instead, the neighbor should provide a proper service: `friend.callFunctionInternallyHandlingStrangerObjects()` | ||
35 | * **Train wrecks** should be avoided and, if necessary, split by defining new variables as intermediate storage. E.g. don't use `obj1.getObj2().getObj3().getObj4()` | ||
36 | * Function chaining can be a valid practice. Don't confuse this concept with train wrecks. **Function chains** are widely used in functional programming, and **method chaining** is a common design pattern. Train wrecks, on the other hand, are anti-patterns for objects that expose their inner workings. | ||
37 | * **Hybrids** occur when classes contain public variables or public accessors/mutators for private variables, thus exposing them. This is called feature envy, because these hybrids can be treated similarly to data structures. Avoid such structures. They have the drawbacks of both concepts, because they make it difficult to add new features as well as new data structures. | ||
38 | * **Hide Internals**: If you know too much about an object, e.g. because you use train wrecks, you should replace them with structures that do not reveal any information about the object. | ||
39 | |||
40 | #### Data Transfer Objects | ||
41 | |||
42 | * **Purpose**: It's a data structure used to transfer data between processes or layers in a software application. | ||
43 | * In **I/O communication** (network traffic, database transactions, reading/writing files), data must be transformed from and to multiple formats that are difficult to read or work with. DTOs are an intermediate step to have a data structure that the developer can easily work with. This means that incoming data needs to be transformed into a DTO and then processed. Outgoing data needs to be put into a DTO and then transformed into the outgoing format. | ||
44 | * **Active record** is a design pattern of a DTO that mirrors the table structure of a database to facilitate the communication process. | ||
45 | * **Process Communication**: Sometimes you need to pass a lot of information from one function to another. Instead of passing many arguments to that function, the cleaner approach is to put all the argument data into a DTO and pass the single DTO instead. | ||
46 | * **DTO Usage**: Use DTOs only at points of data transformation or transfer, especially for I/O operations. Avoid reusing DTOs beyond their original context to maintain their singular responsibility and minimize coupling. Crucially, boundary-centric DTOs should remain distinct from business logic, maintaining a clear separation of concerns. |