logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 Send TopicPrint
Hot Topic (More than 10 Replies) S.O.L.I.D. in UnrealScript (Read 478 times)
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Mar 3rd, 2018 at 12:40pm
Print Post  
This is something I posted first at ut99.org, but I felt like I should also post this here and update accordingly, since it's transversal to both UT99 and Unreal, and even programming in general.



For a while already, I mentioned and advised some folks here to read and apply SOLID principles to how they build their code to their mods.

I was only meaning to talk about this much later on, probably several months from now, after I got my hands in UnrealScript again for real, so I could present better and proven examples, but then it was mentioned that these should be elaborated now, and I think as long there's interest on this subject, doing it now may as well create an interesting discussion and get other programmers to understand, or even criticize, these principles and how to apply them in UnrealScript.

So, what is S.O.L.I.D. ?

It's an acronym which stands for 5 key principles in OOP (Object-Oriented Programming), and they were coined by Robert C. Martin, and have been proven to actually work.
Generally programmers dealing with OOP languages use these principles to guide them into using the best approach to model their classes after how they want them to work, but always in a way that the code is easily maintainable and extensible, something that does not break at the slightest touch.

These 5 principles are the following:
S - Single Responsibility Principle (SRP)
O - Open/Closed Principle (OCP)
L - Liskov Substitution Principle (LSP)
I - Interface Segregation Principle (ISP)
D - Dependency Inversion Principle (DIP)

The first thing to take in mind even before understanding any of these, these are just principles, and NOT laws, which means that while they ought to be followed, at least in my opinion, it does not mean that they can always be applied or that should be. So while ideally you should have all your code following these principles, it does not strictly mean that it has always to.

Also, keep in mind that these are not the only principles code should abide to, there is a number of other principles to take into account when programming anything: DRY, KISS, and others, which won't be touched in this topic, and there is a set of different things called "design patterns" which are just common practices which tend to follow these principles, but in a more specific way, at the implementation level.

Furthermore these principles are towards OOP languages, therefore while other languages may abide by similar principles, different language paradigms may require a completely different set of principles, and even within OOP some of these principles may be hard to downright impossible to exert, such in the case of UnrealScript itself, at least up until Unreal Engine 2.

From here, what I am going to do is to explain each one in a separate post, so this way each post may be linked to separately. Smiley
« Last Edit: Mar 30th, 2018 at 8:58pm by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Reply #1 - Mar 3rd, 2018 at 12:42pm
Print Post  
S - Single Responsibility Principle (SRP)

SRP is, for me, the most important principle of them all, and which, unfortunately, is blatantly violated in the Unreal Engine itself (at least the older ones).
It essentially states the following:
Quote:
A class should have one and only one reason to change, meaning that a class should have only one job.


In other words, this principle states that a class should do just one specific thing.
Rather than being a Swiss Army knife, a class should only be a knife, screwdriver, corkscrew, scissors, etc, but nothing more beyond that.

If you think in terms of testing for example, if you change something like the scissors from a Swiss Army knife, not only you have to test the scissors, now you have to test all the other tools it comes with to check if they still work, if they still open, if you didn't mess up any other mechanism that made all the tools work.
However, if you had split this huge Swiss Army knife into each separate specific tool with just 1 job each, if you changed or improved just the scissors, you don't need to change or test anything on the other tools, because they are independent from each other.

Similarly, when it comes to classes, rather than building everything in one big class, this principle dictates that it's often preferable to split this big class into many smaller ones, each one doing a specific thing.
It makes no sense however to split a big class just for the sake of splitting. While a big class is often a sign of a violation of this principle, it's not always the case, all that there is to it is that a class should be responsible for just one single thing in the entire system, and the simpler the responsibility, the better.


I think the best example I can give you to show exactly this, instead of creating an abstract "foobar" kind of example using this principle, is to give you a clear example of an existing class in the engine which stands as the polar opposite of this principle: Actor.

The Actor class is what's generally called as a God class. While it may sound cool for some, it's the most terrifying way of defining and building a class, and it stands as the most massive and blatant violation of this exact principle.
God classes are classes which alone are responsible for most of the program, if not the entire program, by holding most or even all the functions/methods, most of the properties/members, etc, and which instead of having subclasses to expand with new functionality (simpler to more complex), you have subclasses only using part of the functionality already implemented in such a class (more complex to simpler), while not using the overwhelming majority of the remaining functionality, but still having all its overhead.

Why is Actor a God class?

Let's take a look at what Actor is capable of doing:
http://www.madrixis.de/undox/Source_engine/actor.html

An Actor is capable of: physics, collision, lighting, mesh rendering, texture rendering, sounds, replication, and a LOT more stuff as well.
To not even mention the methods, to do things like execute commands, triggering all sorts of events, tracing, get configuration values, iterators, rendering on the canvas, broadcast messages, give damage, and so on.

In other words, it does too much stuff in the same class, it has too many responsibilities.
And on top of it all, a lot of this stuff is not even used by most of them, such as lighting, and many actors such as Light, BlockAll, Trigger, and many others specialize themselves in using a small part of the functionality already provided by the Actor parent class, rather than being them to implement such a specialized functionality.

How would an Actor look like if it followed SRP?

Well, the best way to understand how it would look like is to look at another engine, such as Unity, which does things right on this regard, where something like lighting, is actually a light component (or class), which can simply be added or attached to another object to give it lighting capabilities.

Therefore, consider the following exemplification of how an Actor could look like if it was better designed by following this principle (along with design by composition):
Code
Select All
abstract class Actor extends Object;

//some basic properties every actor must really have, such as the 3D location in the world

private var Actor nextActor;

final function add(Actor actor)
{
	if (actor == none) {
		return;
	} else if (nextActor != none) {
		actor.nextActor = nextActor;
	}
	nextActor = actor;
}

function tick(float deltaTime)
{
	local Actor a;

	for (a = nextActor; a != none; a = a.nextActor) {
		//update stuff like 3D location
	}
}
 



Then a light could be something just like this:
Code
Select All
class Light extends Actor;

var() enum EType
{
	T_None,
	T_Steady,
	...
	T_TexturePaletteLoop
} Type;

var() enum EEffect
{
	E_None,
	E_TorchWaver,
	...
	E_Rotor,
	E_Unused
} Effect;

var() byte Brightness, Hue, Saturation;
var() byte Radius, Period, Phase, Cone;
var() bool bActorShadows;
var() bool bCorona;
var() bool bLensFlare;
 


Even the "Light" prefixes are no longer necessary to define the names of the properties, looking far better that way, proving that Light does belong to its own class, with the sole responsibility to lit things up.

While a collision could look like this:
Code
Select All
class Collision extends Actor;

var() const float Radius;
var() const float Height;

native(283) final function bool setSize(float radius, float height);
 



And now you can see that now you start having actors specialized in doing a single job: Collision can only collide, while Light can only lit things up.
A Collision cannot lit things up, nor a Light is able to collide with anything, since it's not their job to do so.
However, with this approach, should a need arise that a Collision needs generate light, then a Light could just be used by a Collision class, rather than implementing light in its own code, the latter of which would make the collision actor to be responsible for both colliding and lighting, violating this principle.

And from there, you could also create your own light-weight Actor such as:
Code
Select All
class MyActor extends Actor;

function beginPlay()
{
	local Light l;
	local Collision c;

	l = Spawn(class'Light');
	l.Radius = 240;
	l.Brightness = 128;
	l.Hue = 72;
	add(l);

	c = Spawn(class'Collision');
	c.Height = 8;
	c.Radius = 32;
	add(c);
}
 



As you may have noticed by now, isolating the responsibility of colliding into a separate class would also allow for a single actor to have multiple collisions.
And of course, in case of Unreal Engine in general, this could be something that you could potentially define in the default properties instead, and all the UnrealScript could be actually native code if Epic did it this way, something I think later engine iterations allow to with a specific syntax but in a similar fashion.

Having that said however, the Actor class cannot become the above, so we are all forever bound to the Swiss Army knife of classes forever in this engine, but that should not prevent you from following this and other principles in what you build for yourselves.

And this finishes the first principle, SRP.
If you have anything to add, correct or even criticize, or if you feel like a better example could be used, please feel free to provide any feedback you deem necessary.
« Last Edit: Mar 14th, 2018 at 11:48am by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Reply #2 - Mar 3rd, 2018 at 12:43pm
Print Post  
O - Open/Closed Principle (OCP)

OCP is a principle which, luckily, is fairly well enforced by the original classes in UnrealScript, given that is what allows us to do mods far more easily in the first place, and it essentially states the following:
Quote:
Objects or entities should be open for extension, but closed for modification.


In other words, once you've finished a class to serve a specific purpose, if then you need to modify its source code later, namely modifying existing methods for instance, so the class is able to do something more such as accounting for new classes which were created in the meanwhile, meaning that it became impossible to do so as is or by extending it or even by using another class, you may have just failed at following this principle.

At which point you may ask: does this mean that any source code modification violates this principle?

No.
This principle addresses only a very specific kind of source code modification, and forbids it, and which can be translated to the following: do not hard code or otherwise hard limit anything in your class.

Consider the following example:
Code
Select All
class ArmorCounter extends Actor
{
	var private int count;

	function beginPlay()
	{
		local Pickup p;

		foreach AllActors(class'Pickup', p) {
			if (p.Class == class'Armor' || p.Class == class'Armor2') {
				count++;
			}
		}
	}
}
 


This actor simply counts the number of armor pickups existent in the map, and this is the exact kind of code which violates OCP.

Why?

For instance, let's say that now you decide to extend Armor2 there to create your own armor pickup, simply because you want it to have a new skin, or a new mesh, or something else entirely, but it's still armor nonetheless.
And ArmorCounter which is meant to count the number of armor pickups in the map should be able to account for this new one as well, right?

However, as you may notice, in order for the new armor to be counted, the original class now has to be directly modified, like this:
Code
Select All
if (p.Class == class'Armor' || p.Class == class'Armor2' || p.Class == class'NewShinyArmor') {
	count++;
}
 



And as OCP itself states, this cannot happen, given that what was just done above was a modification to the existing source of ArmorCounter just to account for an extension of another class.
This is the exact kind of required modification that this principle aims to prevent.

So, how should the ArmorCounter class be designed to follow OCP?

As you may have probably already guessed by now, it would look like something like this:
Code
Select All
class ArmorCounter extends Actor
{
	var private int count;

	function beginPlay()
	{
		local Pickup p;

		foreach AllActors(class'Pickup', p) {
			if (Armor(p) != none || Armor2(p) != none) {
				count++;
			}
		}
	}
}
 


or even:
Code
Select All
class ArmorCounter extends Actor
{
	var private int count;

	function beginPlay()
	{
		local Pickup p;

		foreach AllActors(class'Pickup', p) {
			if (p.isA('Armor') || p.isA('Armor2')) {
				count++;
			}
		}
	}
}
 


also works.

Why?

Because now we may safely extend the Armor2 class to our new one with our own shiny new skin and such, but now the ArmorCounter doesn't require any changes whatsoever to account for it, hence being closed for modification, but open for extension (whether this extension is from itself or another class).

And this finishes the second principle, OCP.
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.
« Last Edit: Mar 14th, 2018 at 11:48am by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Reply #3 - Mar 3rd, 2018 at 12:43pm
Print Post  
L - Liskov Substitution Principle (LSP)

LSP is a principle which the game itself pretty much follows most of the time, but it's very easy to violate from the moment you create a subclass.
It's a principle introduced by Barbara Liskov (hence the name), and it states the following:
Quote:
Let q(x) be a property provable about objects x of type T.
Then q(y) should be provable for objects y of type S where S is a subtype of T.

Don't worry if you did not get that definition at all, it took me a few re-reads as well to understand exactly what it meant.

In other words, if you have a class T, and then you extend from it as a new class S (class S extends T), and if you have a function or method somewhere (it does not matter where) defined as q, which may only receive objects x of class T (function q(T x)), then it must be able to safely receive objects y of class S and have S still behave the same as T.

Thus far, this may sound like simple polymorphism, for which languages (such as UnrealScript) generally ensure type safety by enforcing that only T and subtypes or subclasses of T are allowed to be given, whenever you define a function or method parameter of being of type or class T.
However, this principle goes deeper than simple polymorphism, as it addresses a more subtle characteristic of a type or class, for which languages (such as UnrealScript too) generally are not able to enforce whatsoever, and is down to the developers to enforce it instead: behavior.

Behavior is what this principle is all about, and what it really dictates is the following: a subclass must follow the same core behavior as its parent class.

Translating this into a more UnrealScript-oriented view of this principle, consider the following class:
Code
Select All
class Counter extends Actor
{
	var private int count;

	function increment()
	{
		count++;
	}

	function int getCount()
	{
		return count;
	}

	function reset()
	{
		count = 0;
	}
}
 


This is a simple class which acts as a counter, and which increments a count by 1 every time the increment method is called.
Then the current count can be retrieved by calling getCount, and it can be reset through reset.

And this defines the behavior of what this Counter class is meant to do.

Now consider the following class:
Code
Select All
class MyActor extends Actor
{
	var private Counter counter;

	function setCounter(Counter c)
	{
		c.reset();
		counter = c;
	}

	function timer()
	{
		if (counter != none)
		{
			counter.increment();
			if (counter.getCount() == 10) {

				//do stuff

			}
		}
	}
}
 


This is just a class for which you can set a Counter instance by using setCounter, and its count is reset and it's then used in the timer method, where it keeps getting incremented until its count reaches 10.

Up until this point everything is fine.

However, now consider the following Counter subclasses:
Code
Select All
class MyCounter1 extends Counter
{
	function increment() {}
}

class MyCounter2 extends Counter
{
	function increment()
	{
		super.increment();
		super.increment();
		super.increment();
	}
}

class MyCounter3 extends Counter
{
	function int getCount()
	{
		return 10;
	}
}
 


Every single one of the classes above violates LSP.

Why?

The reason is simple: if instead of a Counter instance, I set a MyCounter1, MyCounter2 or MyCounter3 instance through setCounter, MyActor would be broken, given that each one of those 3 subclasses has a very different behavior than the one expected by MyActor:
- MyCounter1 prevents the count to be incremented entirely, therefore timer in MyActor will never finish;
- MyCounter2 increments in steps of 3, therefore timer in MyActor will never finish either, since getCount will never return 10;
- MyCounter3 always returns the same count (10), therefore timer in MyActor will finish right away, instead of going through a full count of 10.

Each one of these 3 behaviors completely contradicts the behavior established in their parent class (Counter), therefore will fail to meet the expectations from any class which uses them and expected not only a Counter type, but a Counter behavior as well, at the very least.
And while it could be argued that the " == 10" could be " >= 10" instead, even because increment can be called from anywhere else in the code, and not exclusively by MyActor, the point here is that increment itself, along the other methods, should not have a different behavior by itself than the one set by the class it was first defined in.

These on the other hand:
Code
Select All
class MyCounter1 extends Counter
{
	function increment()
	{
		super.increment();

		//do other stuff
	}
}

class MyCounter2 extends Counter
{
	function incrementBy3()
	{
		increment();
		increment();
		increment();
	}
}
 


follow LSP perfectly, given that if any of these is given in setCounter, even though MyCounter2 could still break MyActor upon the call of incrementBy3, due to " == " instead of " >= " in the condition, the behavior MyActor expects from increment, getCount and reset is still fully honored, and that's what this principle is all about.

One possible way to ensure LSP is followed in cases like this, is to declare the methods as final, especially the ones which deal directly with private properties (such as count in this case) and are never meant to be overridden or extended in any way by subclasses, however in UnrealScript it is the case that, more often than not, non-final methods and public properties are declared, making this a very important principle to follow whenever possible.

And this finishes the third principle, LSP.
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.
« Last Edit: Mar 14th, 2018 at 2:51pm by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Reply #4 - Mar 3rd, 2018 at 12:43pm
Print Post  
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:
Quote:
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.
« Last Edit: Mar 29th, 2018 at 11:54am by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
S.O.L.I.D. in UnrealScript
Reply #5 - Mar 3rd, 2018 at 12:43pm
Print Post  
D - Dependency Inversion Principle (DIP)

DIP is a principle which is somewhat followed in UnrealScript, in some cases more, and in others less, and it states the following:
Quote:
Entities must depend on abstractions not on concretions.


In other words, a class should not depend on another specific concrete class, and rather it should depend on either an abstract class or an interface.
While interfaces are often preferable for this principle, and while I already explained what an interface is in the previous principle, I will focus this explanation on abstract classes instead given that's what can already be done in UnrealScript.

So, first off, abstract vs concrete classes, what is the difference?

An abstract class is a class which is not meant and cannot be instantiated (spawned) given that it's a class which is meant to represent a base, representing a broader but specific kind or set of classes.
And while it may have a base implementation of some methods and some properties, it generally has methods which are meant to be implemented by subclasses, as the real implementation and usage of such a class is done by extending it into something more concrete: a concrete class.
For example: Decoration, Projectile, Pawn, these are all abstract classes, which by themselves have no substance and aren't usable, as they only represent decoration classes, projectile classes and pawn classes respectively, but no decoration, projectile or pawn in specific.

While a concrete class is a class which is meant to be instantiated (spawned) and it has a specific implementation, as it represents a specific thing which is meant to have an active role in the code or program.
They generally implement what an abstract class has set to be implemented by their subclasses, but are not necessarily required to extend from an abstract class.
For example: the Barrel class is a concrete implementation of the Decoration class, as it represents a barrel, which is something very specific, but it belongs to a bigger and more abstract group of things called decoration, hence being an implementation of Decoration.

What this principle addresses is what the code should always depend on, and it states that the code should depend on abstract, and not on concrete.

Why?

Let's take the following example:
Code
Select All
class RedTeamChecker extends Actor
{
	function bool isSameTeam(byte team)
	{
		return team == 0;
	}
}
 


This class does a very concrete thing: it has a method isSameTeam to check if a given team corresponds to the red team.

Now, let's say we have the following:
Code
Select All
class Door extends Actor
{
	var private RedTeamChecker teamChecker;

	function setRedTeamChecker(RedTeamChecker team_checker)
	{
		teamChecker = team_checker;
	}

	function bool canOpen(byte team)
	{
		return teamChecker.isSameTeam(team);
	}
}
 


This class represents a door, which has a method canOpen to check if a given team can open it or not, and that decision is retrieved from a team checker actor, which this class depends on, which in this case is the RedTeamChecker class, which is a concrete class which only checks for the red team specifically.

So, what's the problem with this?

This is answered with another question: what if I want the door to be able to be opened by the blue team instead?
Or any other team, like green or yellow/gold, or even any other at all that doesn't exist yet?

Well, the obvious answer would be "just extend the RedTeamChecker class into a BlueTeamChecker class and override the isSameTeam method", right?
However, the problem with that approach is that it violates the Liskov Substitution Principle (LSP, the third principle), since a RedTeamChecker should clearly always be a check concerning the red team, and never any other team since the setRedTeamChecker clearly does not expect a class to validate any other team other than red.

Ok... then how about "create a new BlueTeamChecker on the side extending from Actor instead, with similar code, and add a new method setBlueTeamChecker to Door"?
While LSP wouldn't be violated this way, it would violate another principle instead: the Open/Closed Principle (OCP, the second principle), since this means that the Door code would require to be changed to account for every new variation of a team checker class.

Either way, by making Door depend on a concrete class, in order to account for any new changes and extensions, other principles would be violated, and the only way to not violate them is to not change the code at all and keep it concrete and immutable. Which means you're screwed. Grin

So, let's see now an example using an abstraction instead:
Code
Select All
abstract class TeamChecker extends Actor
{
	function bool isSameTeam(byte team);
}
 


Here an abstract TeamChecker class was declared, and, as you can see, the isSameTeam method has no implementation whatsoever.
This is because this class represents a team checker in an abstract way, and for this class it doesn't matter what specific team it needs to check, that's the responsibility of a concrete class to extend it and implement such a concrete detail.

From there, now if we structure the Door class this way instead:
Code
Select All
class Door extends Actor
{
	var private TeamChecker teamChecker;

	function setTeamChecker(TeamChecker team_checker)
	{
		teamChecker = team_checker;
	}

	function bool canOpen(byte team)
	{
		return teamChecker.isSameTeam(team);
	}
}
 


While it will never actually receive an instance from the TeamChecker class itself, it can receive and accept anything which is a concrete implementation of it.
And this is what depending on an abstraction is.

From here, concrete classes could be created to extend TeamChecker in order to be used with the Door, like so:
Code
Select All
class RedTeamChecker extends TeamChecker
{
	function bool isSameTeam(byte team)
	{
		return team == 0;
	}
}

class BlueTeamChecker extends TeamChecker
{
	function bool isSameTeam(byte team)
	{
		return team == 1;
	}
}
 


Any of them could be given as team checkers to a Door, without violating any of the other principles.
And it would be easy to create more team checkers for other teams, even for completely new teams which do not yet exist in the game, and this is why classes should always depend on abstractions, and never on concretions.

And this finishes the fifth principle, DIP.
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.
« Last Edit: Mar 30th, 2018 at 8:59pm by Feralidragon »  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #6 - Mar 6th, 2018 at 10:42am
Print Post  
The second principle (OCP) is done and up: http://www.oldunreal.com/cgi-bin/yabb2/YaBB.pl?num=1520080851/2#2
  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #7 - Mar 14th, 2018 at 11:50am
Print Post  
The third principle (LSP) is done and up: http://www.oldunreal.com/cgi-bin/yabb2/YaBB.pl?num=1520080851/3#3
  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #8 - Mar 29th, 2018 at 11:57am
Print Post  
The forth principle (ISP) is done and up: http://www.oldunreal.com/cgi-bin/yabb2/YaBB.pl?num=1520080851/4#4

This one ended up being quite a challenge to explain in the context of UnrealScript (since there's currently none), unlike my initial estimate, and it ended a bit more lengthy than the others, which is also why it took a lot longer to post.
Having that said, only one principle is left to explain, and is probably the easiest one to explain, so it will come soon. Smiley
  
Back to top
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #9 - Mar 30th, 2018 at 9:00pm
Print Post  
The fifth, and last, principle (DIP) is done and up: http://www.oldunreal.com/cgi-bin/yabb2/YaBB.pl?num=1520080851/5#5

And with that, all SOLID principles are done.
If anyone has any doubts, or even any criticism, either on the principles themselves or how I explained them, or any mistakes I may have made, please do not be afraid, just go ahead and ask or tell whichever is in your mind. Smiley
  
Back to top
IP Logged
 
Turboman.
God Member
Developer Team
*****
Offline


I love YaBB 1G - SP1!

Posts: 858
Location: Somewhere else
Joined: Feb 4th, 2003
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #10 - Apr 1st, 2018 at 8:35pm
Print Post  
I'm just about halfway reading this, but let me say already, many thanks for putting this up!!!
I find it a very interesting read so far. I'm generally not that acquainted with unrealscript but i found it an extremely difficult language to comprehend exactly because of the reasons described above (the principles it opposes). It always surprised me how everything was condensed into this one huge object/actor class (with incomprehensible replication and a huge amount of intrinsic/native references), I always just assumed this was the "way it was meant to be" true "l33t programming" kind of setup used by those people that still counted bytes on a 8mb 133mhz machine Tongue

Though i'm just an unrealscript n00b who knows how to make guns fire 999 projectiles or change a sound or texture here and there, but i do have a bit more knowledge of scripting/programming languages such as java/python/c#/matlab and the likes, and surprisingly i found all of those alot easier to comprehend than unrealscript.
  
Back to top
WWWICQ  
IP Logged
 
Feralidragon
Full Member
***
Offline



Posts: 188
Location: Lisbon - Portugal
Joined: Jul 24th, 2008
Gender: Male
Re: S.O.L.I.D. in UnrealScript
Reply #11 - Apr 2nd, 2018 at 11:05am
Print Post  
You're welcome.  Smiley
As long as it's useful to anyone, the mission has been accomplished.

UnrealScript doesn't violate fully these principles (it could have been much much worse, but fortunately it isn't), but it does violate mostly the one which I personally consider the most important.
And it's very normal for someone with a Java or C# background, or any similar OOP language, to have a bit of a harder time with UnrealScript at first given that not only it violates some principles, it's missing very critical and common things across most OOP languages such as constructors and interfaces for example.

In languages such as Java and C# you may have constructor methods, but not in UnrealScript, which makes it trickier to initialize things the "right way".
What's funny however is that from Unreal Engine 2 (if I recall, or UE3) Epic added a method which is called when any object is first instantiated in an attempt to recover this kind of functionality.

And generally, those languages already offer a lot more out of the box compared to UScript, making it easier to do a lot of stuff.

When I didn't know to do much programming myself, I also thought that given that such a code was done by "professionals" that it was perhaps an exemplar way of doing things, that is until I have learned (from experience alone) that's actually not true.

As a matter of fact, Epic's code is far more inefficient than it should be, especially when accounting the hardware from that time, but I don't really blame them given that it seems that they were just not experienced enough and they were also creating something totally new and of their own, which they didn't have any idea that it would escalate to this point.

It's a bit like PHP's history (the language I work with professionally): it started as something more simpler and uglier for a very specific purpose, since it was never meant to escalate to what it is today.

And as for replication, it's much simpler than it looks.
The fact that it's so embed into the main Actor class with a ton of rather cryptic properties, plus the fact that there isn't a single good easy to understand guide out there, is what makes it harder to perceive, especially from the perspective of anyone who never had to code to run in more a single machine.
And adding to this UScript has some anti-patterned BS features such as "states", which have their own replication rules (which the first principle would have prevented), and then it gets much harder than it should be.

In my case, I only got the whole thing fully once I started to deal and create code for sets of backend machines communicating with each other and scaling horizontally, and with web development in general, and that's when everything about replication finally made full sense to me.

One of these days I intend to make some kind of writeup with pictures, or maybe even a video (who knows) to at least try to explain in the most simple and clear way possible how replication really works.
But I need to get my hands "dirty" again in UnrealScript first, so I don't run the risk of accidentally misleading anyone after 4 years of inactivity in this area, and that won't happen for a while yet.
  
Back to top
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo