I - Interface Segregation Principle (ISP)
ISP is a principle which is pretty much impossible to follow in UnrealScript, at least from the get-go, given that the language itself is missing a critical feature for this principle:
interfaces.
However, I believe it's still worth being mentioned and explained, especially considering that while the language itself does not have the needed feature to enable it, it's still possible to emulate the behavior from an interface in UnrealScript to some extent.
And it states the following:
A client should never be forced to implement an interface that it doesn't use or clients shouldn't be forced to depend on methods they do not use.
So, first off:
what is an interface?
Many OOP languages, and also UnrealScript in Unreal Engine 3, support something called
interfaces.
An interface is something close to a class, but instead it only declares functions or methods to be implemented by other classes, and defines them with no default implementation whatsoever.
Any class which implements an interface,
must implement every single method declared in that interface, and a class may implement multiple interfaces.
What is the purpose of an interface?
An interface is meant to add more capabilities to a class, and do so in a way which is recognizable from other classes and functions that such a class has those specific capabilities, by making an interface act like a
contract which is recognized and strictly followed by every class which implements it.
While a class defines what an object is, an interface defines what an object is able to do.
For anyone who has never heard of interfaces before, this may indeed sound very confusing to grasp at first, but it will get much clearer through some examples.
But since there's no such a thing as interfaces in UnrealScript,
the following examples show an "what if" scenario in case UnrealScript did support interfaces at all:
Code: Select all
interface Describable
{
function string getDescription();
}
This interface defines just a single method to implement:
getDescription, and it gives a class which implements it the capability of returning a description.
And as you can see, it has absolutely no code within the body of the method itself, only the declaration, because it cannot and is not meant to have one, this method is only meant to be implemented in classes themselves.
Now let's say that I intend to create a projectile subclass which has a description of its own, so I implement this interface in it, as such:
Code: Select all
class MyAwesomeProjectile extends Projectile implements Describable
{
function string getDescription()
{
return "This awesome projectile is awesome.";
}
}
By saying that I am implementing the
Describable interface (through
implements Describable), I am now forced to actually implement the
getDescription method the interface declared, and from there on I can retrieve a description from this projectile class.
Easy enough, right?
But we could as easily not implement the interface at all and just declare and implement the same method in this new projectile class, right?
Indeed.
However, the power of an interface is revealed when you want
another completely unrelated class to also
be able to return a description.
For example:
Code: Select all
class MyPawn extends Pawn implements Describable
{
function string getDescription()
{
return "This is just a normal pawn, meh.";
}
}
As you can see, I just created a pawn subclass above, and made it also implement the
Describable interface.
And thus far this still looks rather useless, right?
So now, the real power is revealed:
what if the only thing I care about any class whatsoever, within a function or method, is whether or not it has a description?
Code: Select all
class Broadcaster extends Actor
{
function string broadcastDescription(Describable obj)
{
broadcastMessage(obj.getDescription());
}
}
In the above example,
broadcastDescription is set to accept any object which implements the
Describable interface, and as you can see, here
Describable is now used as the
type of
obj, just like a class, in order to allow this:
Code: Select all
function doStuff()
{
local SomeActor someActor;
local MyAwesomeProjectile proj;
local MyPawn p;
...
someActor.broadcastDescription(proj);
someActor.broadcastDescription(p);
}
Although
MyPawn (as
p) is completely unrelated to
MyAwesomeProjectile (as
proj), since they implement the same interface, which is the interface expected to be implemented by whichever object is given to
broadcastDescription, the code would compile and work with no issues whatsoever.
And this finishes what an interface is.
Getting back to the principle itself (
ISP), it's only about how interfaces should be defined.
In most, if not all, OOP languages which support interfaces, classes may implement multiple interfaces at the same time, they are not limited to just one.
However, an easy mistake to make, and which this principle aims to prevent, is the declaration of too many unrelated methods in a single interface, for example:
Code: Select all
interface TeamDescribable
{
function string getDescription();
function byte getTeam();
}
This interface adds the capability to a class to both retrieve a description and a team.
While this sounds like an useful interface to apply to classes which are team based and have a description, ultimately a description has nothing to do with a team, therefore classes for which only a team makes sense to be returned would still be required to implement
getDescription as well, even if to just return an empty value (empty string), and vice-versa.
However, by
segregating this interface into 2 separate ones, like this:
Code: Select all
interface Describable
{
function string getDescription();
}
interface Teamable
{
function byte getTeam();
}
allows each class to only implement what they really aim to implement.
This way, a class which only has a team, but no description, would only need to implement the
Teamable interface above, like so:
Code: Select all
class SomeTeamActor extends Actor implements Teamable
{
function byte getTeam()
{
return 1;
}
}
while a class which only has a description, but no team, would only need to implement the
Describable interface above, like so:
Code: Select all
class SomeDescriptionActor extends Actor implements Describable
{
function string getDescription()
{
return "This is my description.";
}
}
while a class which has both a description and a team, can implement both the
Describable and
Teamable interfaces above at the same time, like so:
Code: Select all
class SomeTeamDescriptionActor extends Actor implements Describable, Teamable
{
function string getDescription()
{
return "This is my team description.";
}
function byte getTeam()
{
return 1;
}
}
and in the example given above with the
Broadcaster class, which receives an object which implements the
Describable interface in the
broadcastDescription method, it would accept both
SomeDescriptionActor and
SomeTeamDescriptionActor objects since they implement that interface, but would deny (in compile time)
SomeTeamActor objects since they do not implement the expected interface.
Similarly, a function which only accepts
Teamable objects, would accept both
SomeTeamActor and
SomeTeamDescriptionActor, and deny
SomeDescriptionActor.
And this finishes the forth principle,
ISP.
If you have anything to add, correct or even criticize, or if you feel like a better example could be used, once again please feel free to provide any feedback you deem necessary.
In a last note, as first mentioned above, while UnrealScript does not have the ability to create interfaces, which would be extremely useful on many levels, hence being the cornerstone of many OOP languages nowadays, they may effectively be
emulated to some extent by using classes and some mechanisms to dictate type safety and "isA" relationships.
While it would never be comparable to the real thing, both in performance and powerfulness, it would still open the doors to a more powerful style of programming, and could potentially solve many issues concerning dependency management, package mismatching (in a way) and enable classes to have the same set of capabilities without being necessarily inherited from the same parent class, although the way to do it approximates more to "design by composition" than actual interfaces, but a similar principle would still apply.
However, given that such a subject falls outside of the scope addressed here, and the fact that this very post is already a quite lengthy read as it is, I may address it in another separate topic someday, depending on the overall level of interest on this specific subject.