The module is really very similar to a class containing only common members. In fact, in C # there is no such construction as a “module”. You cannot write any application without at least one module or class, so I suspect that your real question is not “why use classes and modules”, but rather “why use several classes and modules and when it’s suitable for starting a new one "Since the modules and classes are essentially the same, I will just focus on why you will have several classes at all. There are four main reasons for creating a new class:
- Saving data in invisible elements
- Organize your code
- Provide seams in the code
- Divide your code into layers and maintain n-levels
Now consider each of them in more detail:
Save data in discrete elements
Often you need to store several data about one element and transfer this data between methods as one object. For example, if you are writing an application that works with a person, you will most likely want to store some data about the person, for example, their name, age and name. You could obviously save these three data as three separate variables and pass them as separate parameters for methods such as:
Public Sub DisplayPerson(name As String, age As Integer, title As String) Label1.Text = name Label2.Text = age.ToString() Label3.Text = title End Sub
However, it is often easier to pass all the data as a single object, for example, you could create MyPersonClass , for example:
Public Class MyPersonClass Public Name As String Public Age As Integer Public Title As String End Class
And then you can transfer all the data about a person by one parameter, for example:
Public Sub DisplayPerson(person As MyPersonClass) Label1.Text = person.Name Label2.Text = person.Age.ToString() Label3.Text = person.Title End Sub
Thus, in the future it will be much easier for you to change a person. For example, if you need to add the ability to store a skill for a person, and you did not put the person’s data in the class, you would need to go to all the places in the code that transfers the person’s data and add an additional parameter. It can be very difficult to find everything in a large project these are places that can be fixed, which can lead to errors. However, the need for a class becomes even more apparent when you begin to keep a list of several people. For example, if you need to save data for 10 different people, you need a list or an array of variables, for example:
Dim names(9) As String Dim ages(9) As Integer Dim titles(9) As String
Of course, it’s not at all obvious that names(3) and age(3) store data for the same person. This is what you just need to know, or you need to write it in a comment so you don't forget. However, this is much simpler and simpler if you have a class to store all the data for a person:
Dim persons(9) As Person
Now it’s quite obvious that persons(3).Name and persons(3).Age are simultaneously data for the same person. Thus, it is self-documenting. No comments are needed to clarify your logic. As a result, again, the code will be less error prone.
Often classes will contain not only data for a particular element, but also methods that act on that data. This is a convenient mechanism. For example, you can add the GetDesciption method to the person class, for example:
Public Class MyPersonClass Public Name As String Public Age As Integer Public Title As String Public Function GetDescription() As String Return Title & " " & Name End Function End Class
Then you can use it as follows:
For Each person As MyPersonClass In persons MessageBox.Show("Hello " & person.GetDescription()) Next
Which, I am sure you will agree, is much cleaner and easier than doing something like this:
For i As Integer = 0 To 9 MessageBox.Show("Hello " & GetPersonDescription(title(i), names(i))) Next
Now let's say you want to keep several aliases for each person. As you can easily see, persons(3).Nicknames(0) much simpler than some crazy two-dimensional array, like nicknames(3)(0) . And what happens if you need to store some data about each nickname? As you can see, not using classes will be very messy.
Organize your code
When you write a long program, it can get confused very quickly and lead to very erroneous code if you misordered your code. The most important weapon you have in this battle against the spaghetti code is to create more classes. Ideally, each class will contain only those methods that are logically directly related to each other. Each new type of functionality should be broken down into a new well-named class. In a large project, these classes should be further organized into separate namespaces, but if you do not divide them into classes, you will really make a mess. For example, let's say that you have all the methods that were thrown into the same module:
GetPersonDescriptionGetProductDescriptionFirePersonSellProduct
I am sure that you will agree that it is much easier to follow the code if these methods have been split into separate classes, for example:
And this is just a very simple example. When you have thousands of methods and variables related to many different elements and different types of elements, I am sure you can easily imagine why classes are important for organizing and self-documenting code.
Provide stitches in code
It may be a little more advanced, but it is very important, so I will try to explain it in simple language. Let's say you create a trace class that writes log entries to the trace log file. For instance:
Public Class TraceLogger Public Sub LogEntry(text As String) ' Append the time-stamp to the text ' Write the text to the file End Sub End Class
Now, let's say you want the registrar class to be able to write to a file or to a database. At this stage, it becomes obvious that writing a file to a file is a separate type of logic that should have been in its own class, so you can split it into a separate class, for example:
Public Class TextFileLogWriter Public Sub WriteEntry(text As String) ' Write to file End Sub End Class
Now you can create a common interface and share it between two different classes. Both classes will process journal entry entries, but each will perform functions differently:
Public Interface ILogWriter Sub WriteEntry(text As String) End Interface Public Class TextFileLogWriter Implements ILogWriter Public Sub WriteEntry(text As String) Implements ILogWriter.WriteEntry ' Write to file End Sub End Class Public Class DatabaseLogWriter Implements ILogWriter Public Sub WriteEntry(text As String) Implements ILogWriter.WriteEntry ' Write to database End Sub End Class
Now that you have violated this data access logic in your classes, you can reorganize your log class in this way:
Public Class TraceLogger Public Sub New(writer As ILogWriter) _writer = writer End Sub Private _writer As ILogWriter Public Sub LogEntry(text As String) ' Append the time-stamp to the text _writer.WriteEntry(text) End Sub End Class
Now you can reuse the TraceLogger class in many other situations without touching the class. For example, you can give it an ILogWriter object that writes entries to the Windows event log or to a spreadsheet or even via email - all without touching the source TraceLogger class. This is possible because you created a seam in your logic between formatting records and recording records.
Formatting doesn't care how records are recorded. All he cares about is formatting records. When he needs to write and write, he simply asks a separate author object to do this part of the work. How and what this author actually does is internally irrelevant. Likewise, the writer does not care how the record is formatted, he simply expects that everything that is passed to him is already a formatted, valid record that must be registered.
As you may have noticed, not only TraceLogger can now be reused to write to any type of log, but also to reuse scripts to write any type of log to these types of logs. You can reuse DatabaseLogWriter , for example, to record trace logs and exception logs.
Little hope for dependency injection
Just joke me a little, as I take this answer a little longer, thinking about something important to me ... In this last example, I used a method called dependency injection (DI). It is called dependency injection because the writer object is a dependency on the log class, and this dependency object is injected into the registrar class through the constructor. You can do something like this without dependency injection by doing something like this:
Public Class TraceLogger Public Sub New(mode As LoggerModeEnum) If mode = LoggerModeEnum.TextFile Then _writer = New TextFileLogWriter() Else _writer = New DatabaseLogWriter() End If End Sub Private _writer As ILogWriter Public Sub LogEntry(text As String) ' Append the time-stamp to the text _writer.WriteEntry(text) End Sub End Class
However, as you can see, if you do this, now you will need to change this log class every time you create a new record type. And then, to create a registrar, you must have links to different types of writers. When you write code this way, pretty soon, every time you include one class, you suddenly have to refer to the whole world to perform a simple task.
Another alternative to the dependency injection approach would be to use inheritance to create several TraceLogger classes, one per record type:
Public MustInherit Class TraceLogger Public Sub New() _writer = NewLogWriter() End Sub Private _writer As ILogWriter Protected MustOverride Sub NewLogWriter() Public Sub LogEntry(text As String) ' Append the time-stamp to the text _writer.WriteEntry(text) End Sub End Class Public Class TextFileTraceLogger Inherits TraceLogger Protected Overrides Sub NewLogWriter() _Return New TextFileLogWriter() End Sub End Class Public Class DatabaseTraceLogger Inherits TraceLogger Protected Overrides Sub NewLogWriter() _Return New DatabaseLogWriter() End Sub End Class
Doing this with inheritance, for example, is better than an enumeration method, because you don't need to reference all the database logic just to enter a text file, but, in my opinion, dependency injection is cleaner and more flexible.
Back to the summary of logical seams
So, in conclusion, the seams in your logic are important for the reuse, flexibility, and interchangeability of your code. In small projects, these things are not of primary importance, but as projects grow, clean seams can become critical.
Another big advantage of creating seams is that it makes the code more stable and verifiable. Once you know that TraceLogger works, there is a big advantage in being able to extend it for future purposes, such as writing logs to a spreadsheet, without having to touch the actual TraceLogger class. If you do not need to touch it, you do not risk introducing new errors and potentially jeopardizing the rest of the code that already uses it. In addition, it becomes much easier to test each piece of code separately. For example, if you want to test the TraceLogger class, you can simply use a fake write object for your test, which is simply written to memory or to the console or something like that.
Divide your code into layers and maintain N-levels
Once you have correctly organized your code into separate classes, where each class is responsible for only one type of task, you can start grouping your classes into layers. Layers are just a high-level organization of your code. There is nothing concrete in the language that makes something technically a layer. Since there is nothing in the language to understand where each layer begins and ends, people often put all the classes for each layer in separate namespaces. So, for example, you might have namespaces that look like this (where each namespace is a separate layer):
MyProduct.PresentationMyProduct.BusinessMyProduct.DataAccess
Typically, you always want to have at least two levels in your code: the presentation or user interface level and the business logic level. If your application performs any kind of data access, which is usually placed in its own layer. Each layer should be as independent and interchangeable as possible. For example, if our TraceLogger class in the above example is in the business layer, it should be reused by any user interface.
Layers extend to all previous topics, providing further organization, self-documentation, reuse and stability. However, another important advantage for layers is that it is much easier to divide your application into several levels. For example, if you need to move the logic of access to business and data access to a web service, it will be very simple to do if you have clearly written your code in certain layers. If, however, all this logic is interconnected and interdependent, then there will be a nightmare to try to break only data access and business logic into a separate project.
The end of what I have to say
In short, you never need to create more than one class or module. You can always write an entire application in one class or module. In the end, all operating systems and software packages were developed before object-oriented languages ​​were even invented. However, there is a reason why Object Oriented Programming (OOP) languages ​​are so popular. For many projects, object orientation is incredibly beneficial.