In object-oriented software development, there are classes, and there are methods. Methods are like functions in a functional programming language with an important difference - they don't exist in isolation. Methods belong to an object. Object-oriented programming methodology encapsulates structure and behavior into a single descriptive unit, the class. Classes describe what structure and what behavior an object may have. They are the object blueprints.
Encapsulating the structure and behavior of an object is how software design scales in size and complexity. A developer that uses an object, doesn't know how the behavior of that object is implemented. Nor do they care. They know everything beneath the surface works as expected. Encapsulation isn't limited to objects. A developer using a function only cares that it works as expected, not how the job is carried out.
Objects, in addition to hiding method implementation, hide structure. These are the attributes that give an object it's identity. Functions have no such structure to hide. An object represents a concept, something that exists in the real world, or some intangible thing. Attributes that describe objects in a software system don't exist in functions. They suffer from a lack of identity, and that is the key difference between functions and methods.
With everything object oriented programming languages promises, are functional programming languages still relevant? Objects are nothing more than a design tool, built with functions, that aid in designing complex systems. Functions are a concise, direct way to supply input and get output. The very idea of a function is an elegant one. However, humans need a better way to understand what they've built and this is where abstract objects help. Functions don't cause a program to fall apart - how they're used do.
Methods are intended to manipulate, or retrieve the values of object attributes. If the attribute values of an object embody the state of the object, methods that change attribute values also change the state of the object. Methods are more likely to have side effects than functions. However, this is the intention of methods - to encapsulate the object behavior, including the changing states of the object. Other objects can be passed to methods as parameters. The state of these objects can, in turn, be changed by the method. Does this violate the encapsulation principle? It doesn't because the object being manipulated doesn't care who does it, as long as the public interfaces of the object are used.
What best describes a software object? The methods that realize it's public interface or the attributes that define it's structure? An object represents some concept while an interface represents some behavior, not bound to any particular concept. Imagine I have a class called Object and I ask you what this object represents based on it's run() method. With this information, we can at best determine that it is something that can run but we can't deduce why the object is able to run. A software program can run. An engine, an animal, a person - all things that can run. What if instead of identifying what the object represents by a method, I asked you to identity it by a structural feature - wheel. A object with a wheel attribute has more of an identity than a object with a run() method.
An interface, not necessarily explicit, describes the behavior of an object, not it's structure. By nature, the methods that implement an interface are a behavioral concept. If a given class implements a Runnable interface, which requires a run() method, it can run. The concept of running is tightly coupled with your class. Interfaces, and their corresponding methods, always need to be implemented, even if that implementation is inherited.
Lets take a step back and compare methods and functions again. Functions are pure behavior with input and output. They don't belong to an object so they can't alter it's state. Conversely, methods belong to an object and alter it's state. As object-based systems grow, so do the number of methods. The side effects of these methods also increase in size.
How can we make methods more like functions and maintain an abstract object design that allows the size of the system to scale? Objects are good at representing some concept with structure. The provided interfaces of those objects, the methods it implements, are the challenge. Lets try making methods their own concepts.
Classes define a special type of method - a constructor. This method is called when the object is first created to do any setup the object may need. An alternative perspective: a constructor is the object's owned behavior. It cannot be called externally once the object exists. A constructor is the essence of the object - it is responsible for making the object what it is.
The constructor method is intended to initialize the computing resources the object needs. Languages like C++ use it to allocate memory. Dynamic languages, like Python, don't share this need. Constructors in these languages are used to initialize the attributes of the object because they aren't statically defined. The constructor may call some other methods to perform some validation, or to read some data from the network or hard drive. Other objects might even be created as the result of a constructor being called. Initialization is the primary focus of the constructor method.
If a class represented nothing but behavior, if it had no structure, the constructor of that class is the behavior. The constructor parameters would be the parameters to the behavior. For example, if Runnable were a class instead of an interface, the constructor would run instead of calling methods defined by the interface. Picture the constructor as a function. Its input the parameters supplied to the constructor. Its output the changes in state to the objects supplied as input. Lets call this type of class a behavioral class.
What about functions that return values? Can behavioral classes return values? Doing so is counter-intuitive because the constructor returns the instance of the behavioral class it self. Instead, the return value is placed in a result attribute of the object. There is a problem with this approach, however. If you were to place the result of creating the behavioral class in an attribute, the object must still exist when the result is used.
On the other hand, traditional methods bound to their objects, need to stick around for the entire life of the object. From an identity perspective, it doesn't make much sense. Once the object is dead, the behavior of that object doesn't really exist either. It might still exist in the blueprint, the class of that object, but that may not necessarily be optimal. Behavior is a concept in its own right.
Methods can be inherited from parent classes. The specialized class has all the generic behavior of the parent. Would a behavioral class negate this aspect of inheritance? Not exactly. If behavior were implemented as a class, it can also inherit general behavior. We have to think differently about how the generalization would work. Traditionally, inherited methods are called in a polymorphic context. That is, they are called when an object that supports the method in question is used. If a method is a class, similar to a function, the constructor needs to call the parent constructor, usually sharing the same signature. Some languages take care of the parent constructor implicitly, like C++.
So can methods as classes, or concepts, actually work? Possibly. Method objects would take care of the behavior identity problem. This approach addresses the notion that behavior is a separate idea from that of object structure. They are similar to functions in that they have no real structure. Methods as concepts, or behavioral classes, can be used in conjunction with existing objects and methods. At least in an experimental sense.
No comments :
Post a Comment