First of all, the examples will be in C#. But I think you'll have no problem understanding it. So...
You CANNOT instantiate objects of an abstract class. You must have a subclass that derives from the class, if you want to instantiate it. But this probably won't help you since you may have come across that before.
So let's try an example.
Suppose you want to lose weight and your nutritionist asks you to track your food intake. When you go to her again, you can't tell her that you ate a "food". Although it's not wrong, since all food has calories, it is too "abstract" for her. So you need to tell her WHICH food.
So, if she were to code that... She'd have an abstract class Food.
abstract class Food
{
public int CaloriesPerPound { get; set; }
//...
}
Why? Because that wouldn't allow you to say "I ate a food".
{
Food fries = new Food(); //This won't even compile
}
So you need concrete classes:
class Fruit : Food
{
//Other stuff related only to fruits
}
class Bread : Food
{
//Other stuff related only to Bread
}
So you'll have to tell exactly which food it is.
{
Apple desert = new Apple(); //Now we are talking
}
But she still needs the food class, it is still interesting to her:
class Meal
{
public List<Food> Foods { get; set; }
//Other meal related stuff
public int TotalCalories()
{
int calories = 0;
foreach (Food food in Foods)
{
calories = calories + food.CaloriesPerPound * FoodQuantity;
}
return calories;
}
}
That's an example of when you need an abstract class. When you want to have a base class, but you don't want anyone creating an object of your class.
An abstract CAN have abstract methods. It doesn't need to. Is totally ok to have an abstract class without abstract methods. Speaking of which...
Abstract method is something that it's too broad. There's no similarities on how things work, so the classes that derive from your class that has an abstract method won't be able to call the super class implementation. And you're also forcing all the sub classes to implement their version of that method.
Let's try another example? An Animal Band Simulator. You'd have animals, and each one of them would make a sound. But those are very different "implementations", they have nothing in common A dog differs from a cat, a cat from an elephant, an elephant from a cricket. So you'd have an abstract method MakeSound.
class Animal
{
public abstract void MakeSound();
}
But, if you have an abstract method in your class, that makes the whole class abstract!
abstract class Animal
{
public abstract void MakeSound();
}
So the rest of the code might be:
class Dog : Animal
{
public void MakeSound()
{
//bark at the moon
}
}
class Cat : Animal
{
public void MakeSound()
{
//meowing for food
}
}
And the BAND!
class AnimalBand
{
public List<Animal> Animals { get; set; }
public void RockOn()
{
foreach (Animal animal in Animals)
{
animal.MakeSound();
}
}
}
So this is an example of when you use an abstract method.
I guess this is the essence. It may be a little forced/convoluted, but I think it gets the point across. Go back to the examples that didn't help you before and see if they make sense now.
I'm go out on a limb and say you're a beginner. This stuff may seem strange to you, and you may not see any benefit in using abstract classes.
It's ok. It's strange for almost all of us when we start. But in time, you'll learn to appreciate it.
A common suggestion is to put all the logic into each Pdf
object. But this requires that you extend the interface of Pdf
and amend the implementation of each Pdf
object each time want to support a new operation. It also requires that each Pdf
object has to know everything about how to operate on it, rather than just knowing how to store its own representation. The result is that you end up with giant Pdf
objects that basically have to do everything.
You probably want to use the Visitor Pattern to accomplish this.
Standard Visitor Pattern
Add a new method to your Pdf
base class:
class Pdf {
...
public abstract void AcceptVisitor(IPdfVisitor visitor);
}
The IPdfVisitor interface is fairly simple:
interface IPdfVisitor {
void Visit(FirstPdf pdf);
void Visit(SecondPdf pdf);
void Visit(ThirdPdf pdf);
}
Now, each Pdf class object you implement defines the AcceptVisitor function in the same way (but you need to write the implementation in each class).
class FirstPdf : Pdf {
...
public override void AcceptVisitor(IPdfVisitor visitor) {
visitor.Visit(this);
}
}
So, why do you have to have the implementation in each class? Because the compiler knows the static type of each class. So when you call visitor.Visit(this)
from an object of type FirstPdf
, it will call the Visit function that accepts an argument of type FirstPdf
. Now, you implement visitors for each "verb" you want to implement. Maybe you have a SavePdfVisitor
, a PrintPdfVisitor
, and a DisplayPdfVisitor
, for example. They would look like this:
class SavePdfVisitor : IPdfVisitor {
public void Visit(FirstPdf pdf) {
// FirstPdf specific save logic
}
public void Visit(SecondPdf pdf) {
// SecondPdf specific save logic
}
public void Visit(ThirdPdf pdf) {
// ThirdPdf specific save logic
}
}
Now, your generic save method for Pdfs looks like this:
public void Save(Pdf pdf) {
IPdfVisitor saveVisitor = new SavePdfVisitor();
pdf.AcceptVisitor(saveVisitor);
}
The pdf
object calls Visit
and passes itself to the save visitor, without needing to know what type of operation is being performed. And the correct method of SavePdfVisitor gets called. At a minimum, this lets you put all Save logic into a single place and use private methods for shared functionality.
One advantage of this is that if you define a new type of Pdf
, when you implement its AcceptVisitor
method, you will get a compiler error unless you add that type to the IPdfVisitor
interface and then to each of the visitor objects. This guarantees that when you define a new type of Pdf
, you don't forget to implement the logic for it for all the operations you perform on Pdf
's.
Better Visitor Pattern (In My Opinion)
On the other hand, if you have a lot of shared logic -- for example, the save operation is the same for most Pdf
types but is custom for just a few -- you can define your visitor objects using an abstract base class instead of an interface like this.
abstract class PdfVisitor {
public abstract void Visit(Pdf pdf);
public virtual void Visit(FirstPdf pdf) {
Visit((Pdf)pdf);
}
public virtual void Visit(SecondPdf pdf) {
Visit((Pdf)pdf);
}
public virtual void Visit(ThirdPdf pdf) {
Visit((Pdf)pdf);
}
}
class Pdf {
...
public abstract void AcceptVisitor(PdfVisitor visitor);
}
class FirstPdf : Pdf {
...
public override void AcceptVisitor(PdfVisitor visitor) {
visitor.Visit(this);
}
}
// Do the same for other Pdf classes
Notice that PdfVisitor
does NOT define the implementation of Visit(Pdf pdf)
. It does, however, provide a default implementation of Visit
for all the concrete implementation types. That default implementation just calls Visit(Pdf pdf)
. When you implement Visit(Pdf pdf)
in each of your concrete visitor objects, that defines the default behavior for that visitor or action. For example, suppose that your DisplayPdfVisitor
uses the same logic for every type of Pdf
. You just implement it this way:
class DisplayPdfVisitor : PdfVisitor {
public override void Visit(Pdf pdf) {
// Display logic here
}
}
On the other hand, if SavePdfVisitor
does the same thing for all Pdf
objects, except that SecondPdf
needs a special implementation, do this:
class SavePdfVisitor : PdfVisitor {
public override void Visit(Pdf pdf) {
// default save implementation
}
public override void Visit(SecondPdf pdf) {
// custom save implementation for SecondPdf type
}
}
Now if you call pdf.AcceptVisitor(saveVisitor)
on a Pdf
object of type SecondPdf
and it calls visitor.Visit(this)
, it will call the method that accepts an object of type SecondPdf
. However, the same code path for any other type of Pdf
will just call the default implementation.
The advantage of this approach is that you can define a new Pdf
operation by creating a visitor that just implements a single method and it will apply to all object types. You can also define a new type of Pdf
object and it will get default behavior on all your visitors without having to make any changes. If you create a new Pdf
class, say FourthPdf
, and you realize that you need custom logic for it in your DisplayPdfVisitor
then you just modify the base class:
abstract class PdfVisitor {
// previous stuff
public virtual void Visit(FourthPdf pdf) {
Visit((Pdf)pdf);
}
}
And then in DisplayPdfVisitor
:
class DisplayPdfVisitor : PdfVisitor {
// other implementation
public override void Visit(FourthPdf pdf) {
// custom FourthPdf display logic
}
}
You didn't have to change SavePdfVisitor
or PrintPdfVisitor
or anything else. They gladly apply the default save and print behavior to FourthPdf
objects, but DisplayPdfVisitor
handles them differently.
Best Answer
This may be a personal preference, but I avoid having base classes for the sake of sharing properties. Particularly in data classes. I don't mind the repetition, and I avoid locking my classes into a fixed "data" hierarchy.
There are enough problems with inheritance, particularly over time as the code base grows, that I am reluctant to share methods through a class hierarchy, and even more reluctant to share properties through a class hierarchy.
...
Hm. I just noticed that you mentioned "static" properties. Rather than static (i.e. global) properties in a base class, you're better off with a separate, single instance of something dispensed by a factory. Make sure you provide for concurrency to avoid race conditions.