Update
This commit is contained in:
parent
8b1d7a4381
commit
aadf27a49f
16 changed files with 2151 additions and 1804 deletions
321
oop.md
321
oop.md
|
@ -64,4 +64,323 @@ Once computers start fundamentally working on a different paradigm, e.g. functio
|
|||
|
||||
## History
|
||||
|
||||
TODO
|
||||
TODO
|
||||
|
||||
## Code Example
|
||||
|
||||
OK so let's dive into this for the sake of demonstration, here is some kind of [C++](cpp.md) code along the lines of a typical OOP textbook example:
|
||||
|
||||
```
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Animal // abstract class
|
||||
{
|
||||
protected:
|
||||
static int animalsTotal;
|
||||
const char *name;
|
||||
|
||||
public:
|
||||
Animal(const char *name);
|
||||
const char *getName();
|
||||
virtual void makeSound() = 0;
|
||||
static int getAnimalsTotal();
|
||||
};
|
||||
|
||||
int Animal::animalsTotal = 0;
|
||||
|
||||
int Animal::getAnimalsTotal()
|
||||
{
|
||||
return animalsTotal;
|
||||
}
|
||||
|
||||
class Cat: public Animal // cat is a subclass of animal
|
||||
{
|
||||
protected:
|
||||
int treesClimbed;
|
||||
|
||||
public:
|
||||
Cat(const char *name);
|
||||
virtual void makeSound();
|
||||
void climbTree();
|
||||
};
|
||||
|
||||
class Dog: public Animal // dog is a subclass of animal
|
||||
{
|
||||
protected:
|
||||
int ballFetched;
|
||||
|
||||
public:
|
||||
Dog(const char *name);
|
||||
virtual void makeSound();
|
||||
void fetch();
|
||||
};
|
||||
|
||||
Animal::Animal(const char *name)
|
||||
{
|
||||
this->name = name;
|
||||
animalsTotal++;
|
||||
}
|
||||
|
||||
const char *Animal::getName()
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
Cat::Cat(const char *name): Animal(name)
|
||||
{
|
||||
this->treesClimbed = 0;
|
||||
}
|
||||
|
||||
Dog::Dog(const char *name): Animal(name)
|
||||
{
|
||||
this->ballFetched = 0;
|
||||
}
|
||||
|
||||
void Cat::climbTree()
|
||||
{
|
||||
this->treesClimbed++;
|
||||
}
|
||||
|
||||
void Dog::fetch()
|
||||
{
|
||||
this->ballFetched++;
|
||||
}
|
||||
|
||||
void Cat::makeSound()
|
||||
{
|
||||
cout << "meow";
|
||||
}
|
||||
|
||||
void Dog::makeSound()
|
||||
{
|
||||
cout << "woof";
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
#define ANIMALS 5
|
||||
|
||||
Animal *animals[ANIMALS]; // pointers to animals
|
||||
|
||||
animals[0] = new Cat("Mittens");
|
||||
animals[1] = new Dog("Doge");
|
||||
animals[2] = new Cat("Mr. Jinx");
|
||||
animals[3] = new Cat("Toby");
|
||||
animals[4] = new Dog("Hachiko");
|
||||
|
||||
cout << "There are " << Animal::getAnimalsTotal() << " animals in total:" << endl;
|
||||
|
||||
for (int i = 0; i < ANIMALS; ++i)
|
||||
{
|
||||
cout << animals[i]->getName() << ": ";
|
||||
animals[i]->makeSound();
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ANIMALS; ++i)
|
||||
delete animals[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
It should write out:
|
||||
|
||||
```
|
||||
There are 5 animals in total:
|
||||
Mittens: meow
|
||||
Doge: woof
|
||||
Mr. Jinx: meow
|
||||
Toby: meow
|
||||
Hachiko: woof
|
||||
```
|
||||
|
||||
Now let's quickly go over the code (it's really a super quick summary, if you don't understand it grab some book on OOP).
|
||||
|
||||
The code defines 3 classes. The first class, `Animal`, represents an animal, it has an attribute called `name` that records the animal's given name. There is also a static attribute called `animalsTotal` -- static attribute means it belongs to the class itself, NOT the objects, i.e. it's basically a global variable that's just associated with the class. The class also has methods, such as `getName` that simply returns the animal's name or the `getAnimalsTotal` method -- this one is however a static method, meaning it belongs to the class and can be called without any object at hand. The `Animal` class is abstract, which means we cannot make objects of this class directly, it only serves as a base class for further subclasses. In C++ abstract class is any class that has at least one pure virtual methods, here the method `makeSound` -- such method is marked with `= 0` after it, which means it doesn't have an implementation (it doesn't have to be implemented as there won't be any objects of this class on which the method could be called). Then there are two subclasses of `Animal`: `Cat` and `Dog`. These are no longer abstract, i.e. we will be able to make cat and dog objects; these subclasses inherit the attributes of the parent class (the `name` attribute, i.e. cats and dogs will have their names) and methods, such as `getName` -- this method is a "normal" method, it behaves the same for all animal classes, it just returns the name, there's nothing special about it. However note the method `makeSound` -- this is a virtual method, meaning it will behave differently for each specific class, i.e. cat makes a different sound than dog -- so each of these classes has to implement its version of this method. Notice the methods that have the same name as the class (e.g. `Cat::Cat` or `Dog::Dog`) -- these are called constructors and are automatically called every time an object of the class is created. In the `main` function we then create an array of 5 `Animal` pointers, i.e. pointers that can point to any animal subclass; then we create some cats and dogs and let the array point to them. Then we iterate over the array and call each object's `makeSound` method -- this demonstrates so called [polymorphism](polymorphism.md): we don't care what the object is (if it's a cat or dog), we always call the same `makeSound` method and the language makes it so that the correct version for the object is called. Polymorphism is kind of one of the highlights of OOP, so it's good to stress it here -- it is connected to the virtual methods.
|
||||
|
||||
Now let's see how we could emulate this OOP code in a non-OOP language, just for the sake of it -- we'll do it in C (note that we are really trying to closely emulate the code above, NOT solve the problem the way in which it would normally be solved without OOP). It may look something like this (it can potentially be done in different ways, of course):
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _treesClimbed;
|
||||
} Cat;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _ballsFetched;
|
||||
} Dog;
|
||||
|
||||
typedef struct _Animal
|
||||
{
|
||||
const char *_name;
|
||||
void (*makeSound)(struct _Animal *);
|
||||
|
||||
union
|
||||
{
|
||||
Cat cat;
|
||||
Dog dog;
|
||||
} subclass;
|
||||
} Animal;
|
||||
|
||||
int _AnimalAnimalsTotal;
|
||||
|
||||
void AnimalNew(Animal *this, const char *name)
|
||||
{
|
||||
this->_name = name;
|
||||
_AnimalAnimalsTotal++;
|
||||
}
|
||||
|
||||
int AnimalGetAnimalsTotal(void)
|
||||
{
|
||||
return _AnimalAnimalsTotal;
|
||||
}
|
||||
|
||||
const char *AnimalGetName(Animal *this)
|
||||
{
|
||||
return this->_name;
|
||||
}
|
||||
|
||||
void CatMakeSound(Animal *this)
|
||||
{
|
||||
printf("meow");
|
||||
}
|
||||
|
||||
void DogMakeSound(Animal *this)
|
||||
{
|
||||
printf("woof");
|
||||
}
|
||||
|
||||
void CatNew(Animal *this, const char *name)
|
||||
{
|
||||
AnimalNew(this,name);
|
||||
this->subclass.cat._treesClimbed = 0;
|
||||
this->makeSound = CatMakeSound;
|
||||
}
|
||||
|
||||
void DogNew(Animal *this, const char *name)
|
||||
{
|
||||
AnimalNew(this,name);
|
||||
this->subclass.dog._ballsFetched = 0;
|
||||
this->makeSound = DogMakeSound;
|
||||
}
|
||||
|
||||
void CatClimbTree(Animal *this)
|
||||
{
|
||||
this->subclass.cat._treesClimbed++;
|
||||
}
|
||||
|
||||
void DogFetch(Animal *this)
|
||||
{
|
||||
this->subclass.dog._ballsFetched++;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
#define ANIMALS 5
|
||||
|
||||
Animal *animals[ANIMALS];
|
||||
|
||||
animals[0] = malloc(sizeof(Animal));
|
||||
CatNew(animals[0],"Mittens");
|
||||
|
||||
animals[1] = malloc(sizeof(Animal));
|
||||
DogNew(animals[1],"Doge");
|
||||
|
||||
animals[2] = malloc(sizeof(Animal));
|
||||
CatNew(animals[2],"Mr. Jinx");
|
||||
|
||||
animals[3] = malloc(sizeof(Animal));
|
||||
CatNew(animals[3],"Toby");
|
||||
|
||||
animals[4] = malloc(sizeof(Animal));
|
||||
DogNew(animals[4],"Hachiko");
|
||||
|
||||
printf("There are %d animals in total:\n",AnimalGetAnimalsTotal());
|
||||
|
||||
for (int i = 0; i < ANIMALS; ++i)
|
||||
{
|
||||
printf("%s: ",AnimalGetName(animals[i]));
|
||||
animals[i]->makeSound(animals[i]);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
for (int i = 0; i < ANIMALS; ++i)
|
||||
free(animals[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Here we implement the virtual methods with function pointers. We use normal functions instead of class methods and simply have their names prefixed with the class name. Inheritance is made with an union holding the subclass stuff. Private things are prefixed with `_` -- we rely on people respecting this and not accessing these things directly.
|
||||
|
||||
Now let's see how we'd solve the same problem in C in a natural way:
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
#define ANIMAL_CAT 0
|
||||
#define ANIMAL_DOG 1
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int type;
|
||||
const char *name;
|
||||
} Animal;
|
||||
|
||||
int animalsTotal = 0;
|
||||
|
||||
Animal animalNew(int type, const char *name)
|
||||
{
|
||||
animalsTotal++;
|
||||
|
||||
Animal a;
|
||||
|
||||
a.type = type;
|
||||
a.name = name;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void animalMakeSound(Animal *animal)
|
||||
{
|
||||
switch (animal->type)
|
||||
{
|
||||
case ANIMAL_CAT: printf("meow"); break;
|
||||
case ANIMAL_DOG: printf("woof"); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
#define ANIMALS 5
|
||||
|
||||
Animal animals[ANIMALS];
|
||||
|
||||
animals[0] = animalNew(ANIMAL_CAT,"Mittens");
|
||||
animals[1] = animalNew(ANIMAL_DOG,"Doge");
|
||||
animals[2] = animalNew(ANIMAL_CAT,"Mr. Jinx");
|
||||
animals[3] = animalNew(ANIMAL_CAT,"Toby");
|
||||
animals[4] = animalNew(ANIMAL_DOG,"Hachiko");
|
||||
|
||||
printf("There are %d animals in total:\n",animalsTotal);
|
||||
|
||||
for (int i = 0; i < ANIMALS; ++i)
|
||||
{
|
||||
printf("%s: ",animals[i].name);
|
||||
animalMakeSound(&(animals[i]));
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Notice the lack of bullshit. OOPers will argue something about scalability or something, but that's argument of [bloat](bloat.md) so it's invalid -- basically they tell you "just wait till you have 10 million lines of code, then it becomes elegant", but of course, such code is already bad only by its size -- code of such size should never be written. They will also likely invent some highly artificial example tailored to suit OOP which you will however never meet in practice -- you can safely ignore these.
|
Loading…
Add table
Add a link
Reference in a new issue