Object-Oriented Programming

Programming paradigm – there are many different programming paradigms, which are ways of thinking or schools of thought for how to do things. Imperative is very straightforward – do a series of commands. Procedural is stuff that happens based on previous things that happened. Object-oriented, the current prevailing paradigm, uses classes and objects and hierarchies for inheritance/polymorphism. Functional programming is different and not something I’m super familiar with, but all I know is that some people in academia are really into it, but very few people in the real world use it. Functional programmers will write research papers or give long-winded explanations of why it’s good, but if it’s so good, how come most of the code we rely on every single day is object-oriented instead? Object-oriented programming, or OOP, is what you should stick with.

Many languages are multi-paradigm, at least to some degree, meaning they incorporate traits from multiple programming paradigms rather than being purely one or another. However, even multi-paradigm languages might be 80% one paradigm and 10% another and 10% some other paradigm. They’re not all equally influenced. I suggest sticking with OOP-focused multi-paradigm languages. You will find that some OOP languages will add a functional feature here and there, like Java adopting lambda expressions. Java is mostly an OOP language, but lambdas are borrowed from the functional paradigm. Also, please note that functions are not the same as functional programming. You can have functions in an OOP language, but that doesn’t make it part of the functional programming paradigm.

Encapsulation – The idea of encapsulation is simple – everything should be self-contained. A function should do all of its stuff internally and not have all sorts of calls to other objects and whatnot. Think of a capsule. It has its contents, and it stores them in such a way that it’s separate from what’s outside of it. Encapsulation-minded development staves off spaghetti code.

Struct – similar to a class, something you will encounter in C++. The big difference is that everything is public by default in a struct. One of my professors said that structs should be used differently than classes, using structs for things with properties but not so much for actions. Honestly, you can probably get by using classes instead. However, some people have their own preferences of how they do things. When you’re just starting out, being overwhelmed with a lot of similar-sounding terms can confuse you, so I’d say skip structs until you’ve mastered classes first.

I pretty much always use classes and never use structs unless I have to.

Class – blueprints for objects. If you’re writing an RPG game, you might create a class for a monster. Using that one class, you can make as many copies of it as you want. A class does nothing on its own, as it is just a blueprint or recipe. Class names are usually capitalized.

Object – an object is an instance of a class. Objects are sometimes called instances. If you bake a pie using a recipe, the recipe is the class and the pie is the object. You can make as many objects from a single class as you want (provided you have the computing resources, such as RAM and CPU time). Objects reside in memory, and you will lose their state if you close the program or turn off your computer. But if you want to convert RAM contents to persistent storage or vice versa, you can use serialization and deserialization.

Instantiation – the process of making an object, or instance, from a class.

Generics or templates – using generics will allow you to create methods that can handle multiple data types. Let’s say I want to make a multiplication function in a language with strong static typing. You could make one method that multiplies two integers, and another that multiplies floats, or you can use generics so that it can handle either one. In Java, this concept is referred to as generics. In C++, these are templates.

Generics use <> to define types. Let’s say you have a generic class. To create an instance of a generic class, you need to do something like this (in Java):

GenericClass<int> myCoolInstance = new GenericClass<int>(“argument”);

That means it’s being used with integers.

Generics can be used for classes or methods/functions.

Raw types – when you use generic classes (which have at least one formal type placeholder) without specifying types, they’re using raw types. This is not good. It’s better to declare a generic class object with type parameterization. In plain English, when you use generics and specify the type to be used with it, it’s fine. If you don’t, it’s bad.

Always do something like this:

SomeGenericClass<String> myObject = new SomeGenericClass<String>();

myObject.add(“This is a string”);

Never do something like this:

SomeGenericClass myObject = new SomeGenericClass();

The difference is that the first one is using String as its type, and the second one doesn’t specify any. In the first one, you will get a compile-time error if you try to add anything that isn’t a String to the generic object with the specified type, but you can have multiple types with the raw type generic object, which can lead to run-time issues, but your compiler won’t care.

Instance variable – variables that are specific to an instance of a class. If you have a Person class, the instance variables might be a firstName and lastName. If the instance is deleted, the instance variables go away too. For encapsulation, for the most part, only the instance should deal with its own instance variables, though there are some times when you want to return something, like with a getter or setter.

Constructor – a special function within a class that defines how the object gets initialized.

I wrote a Python program that, among other things, lets you save your work as a project with files in a folder. I wrote a project class so you can create project objects it. Here is a constructor I wrote for the project class:

def __init__(self, project_name=”example”):

if self.validate_name(project_name):

self.project_name = project_name

else:

print(“invalid project_name, sticking with example instead”)

This particular constructor has a couple things that are more advanced than simple constructors you might make when starting out. But it’s good to know the features.

__init__ is reserved for constructors in Python. It’s short for initialization. def is short for define. You have to pass self to the constructor, as this is an instance of a class (I just didn’t include the entire class code here because it’s long). There’s also a second constructor argument with a default value of “example”.

I wrote a function to validate input, called validate_name(). If the project name is valid, the project object gets its name instance variable set to what was passed as an argument. If not, it retains the “example” value as a placeholder/backup instead.

If you do not write a constructor for your class, it will automatically be given a default constructor, which doesn’t do much.

this or self: qualifiers to specify the instance’s version of something. Which word gets used depends on the language you’re using.

this.first_name = name_from_constructor

Overloading – a class can have multiple constructors as long as they have different kinds of arguments. Only one constructor is used for any given instantiation, and which constructor gets used in an overloaded constructor scenario is determined by the arguments provided when creating an object. You can think of it as conditional function invocation disambiguated by arguments provided by the caller.

If you make a Person class, you could have two constructors: one for taking no arguments, which gives the object a default first and last name. Or you could also use its second constructor, which takes two string arguments: one for the first name and another for the last. Not all programming languages support constructor overloading. For example, Python does not support constructor overloading, but Java does. Additionally, there are other kinds of overloading, such as operator overloading.

Operator overloading – when an operator has different uses depending on the operands. For example, + means add when you give it two numeric operands, such as integers: cool_number = 1 + 1. But + means concatenate strings when you give it string operands, like this:

print(“Hello there, ” + user_name)

Concatenate means to put together. If you concatenate “hello ” with “world”, then the result will be “hello world”. If you have a string of “4” (not a numeric type, as indicated by the quotes!), and concatenate it with “4” then the result will be “44”. But if you convert the aforementioned strings to integers first, using type casting/conversion, then you can add them and get a value of 8. They both use the same operator, +, but different things happen depending on the types of the operands that are used with it.

Destructor – Constructors bring objects into the world. Destructors take them out of it. Some garbage-collected languages don’t have destructors. In languages with manual memory management, like C++, for every new, you need a delete. Otherwise, you can get memory leaks. You won’t be using destructors in languages like Python or Java though. Destructors are often associated with tildes (~).

Access modifiers – A class can have methods and attributes (variables). But maybe you only want specific methods or attributes to be accessible by certain things. Maybe you don’t want someone to be able to use a function that is only supposed to be used by another function within the same class, triggered during initialization or something, as opposed to being freely used by anyone.

You can make something public to allow anyone to access it. Alternatively, you can make something private to make it only able to be used by the class object itself. Or you can make something protected in order to make it so that the class and subclasses can use it. Access modifiers are used heavily in Java, but not at all in other languages.

Changing a cat’s name in Java might look something like this:

myCat.setName(“Whiskers”);

But in Python, you might do something like this:

my_cat.name = “Whiskers”

Having no access modifiers means there is less complexity, but in the Java example, the setName() method can validate the name change to make sure it’s something acceptable, based on type, length, and so on. In the Python example, it doesn’t check anything at all, which can lead to problems. It’s a tradeoff, but I tend to dislike Java’s enterprise boilerplate because it seems like you’re often writing the same bits and pieces of code in all your programs. Accessors and mutators for everything. It’s an antipattern, which is not a good thing. Keep in mind – you have to write the setters and getters yourself. It’s a solution to a problem created by access modifiers.

Method – Sometimes the terms function and method are used interchangeably, but other times there is a key distinction between them. Some programming languages call methods functions, but some have two separate things: a function can be a function on its own, whereas a method is a function that corresponds to an instance of a class. So you can call a function completely independent of objects, such as do_thing(), but a method would be something like some_object.do_thing().

Mutator – also called a setter, it lets you change something. Instead of accessing something directly, mutators are used so that there can be validation of the value you propose to set it to. This can stop attributes of an object from being set to invalid values.

personObject.setHomeAddress(“123 Cool Street”);

Without a mutator, you could instead have public access modifiers to allow direct access to the instance variables. For example:

personObject.homeAddress = “123 Cool Street”;

But because that’s directly accessing a variable, rather than invoking a mutator/setter method, you can’t perform any input validation that way, so it’s possible that the person using it this way could set a variable to something it’s not supposed to be.

Accessor – also called a getter, it lets you get something from a class.

personObject.getHomeAddress()

toString() – In Java, some things come with toString() methods, and other times, you need to write one yourself. The purpose of a toString() is to return a string that contains all the info about an object. If you have a Person class, and it has their first name, last name, age, and height, your toString() should concatenate everything to a string, typecasting when necessary (such as with the age and height values, which will be numeric types such as integers rather than strings), and then return it.

Abstract class – a special type of class that you’re not allowed to make instances of. It is often used with inheritance. An example of an abstract class could be a Sedan class, with subclasses such as Ford Focus or Toyota Camry. Do you know of a single animal that is only called mammal? No. All mammals have a more specific name, though they are still mammals.

If you made a Soda abstract class (and it needs to be capitalized when it’s a class), it could have an attribute called sweetness. It could also have a method called fizz(), which just prints “fizz” or something. The point I’m trying to make here is that abstract classes contain generic stuff that all the sub-types have so that you don’t need to repeat yourself for every specific type. If all Soda sub-classes have sweetness and fizzing, why would you want to repetitively implement it in all of the individual kinds when you can just save time by putting the shared stuff in an abstract class that every kind of soda inherits from? Then every subclass automatically gets it (assuming there are either public or protected access modifiers).

Interface – something can use an interface when it has it, rather than is it. Class: is-a. Interface: has-a.

When you want to add an interface to something, you use the keyword implements. Inheritance is done via the extends keyword. You can use inheritance and interfaces.

I am a human (an instance of the Human class), so you could say I inherit (or extend) from the Mammal superclass (classes should be capitalized) because I am a more specific version of a mammal. If I have a skateboard, I implement the skateboard. I am not a skateboard, nor am I a descendant of a skateboard, so it would not be inheritance. Interfaces are when you have something, not when you are something.

Here is an example of a class definition in Java that represents what I was talking about. There is nothing in the body of the class (within the curly braces), but that’s not the point right now.

public class Human extends Mammal implements Skateboard{}

//class definition goes in curly braces

Functional interface – an interface with only one abstract method. A class that implements a functional interface must override is abstract method.

Inheritance – when a subclass extends a superclass, it inherits things from it. A Pet superclass might have generic properties and actions, but the Cat subclass might inherit them, add its own stuff, or even change things in a polymorphic fashion.

Multiple inheritance – Java does not allow multiple inheritance. If something inherits from the Pet class, it can’t also inherit from the Frisbee class. Some languages might allow for multiple inheritance, but then it can get kind of confusing, especially if both superclasses have similarly-named attributes.

In the real world, sometimes your classes won’t involve unnecessarily complicated hierarchies of classes and subclasses (or even subclasses of subclasses). Sometimes you can use a base class, or sometimes not make any classes at all in your program, just using primitive data types and things that already exist in the standard library. There is nothing wrong with that. Some simple programs don’t need to be overly complicated just for the sake of implementing things you learned.

Overriding – when a subclass inherits something and then changes it, it’s called overriding. Maybe a Pet class has a method called greet(), which prints “hello.” But a Cat subclass can override the method and make the greet() method print “meow” instead.

In Java, you have to use @Override before the thing to be overridden.

For example:

@Override

public void greet() {

System.out.println(“meow”);

}

Superclass – A class that a subclass inherits from. A Lizard class’s superclass might be Reptile.

Superclass constructor – a subclass’s constructor might need to invoke its superclass constructor with certain arguments. If no superclass constructor call is made, the default superclass constructor will be used, if applicable. Use super() in Java to invoke the superclass constructor. It’s generally a good idea to use a superclass constructor before proceeding with the subclass’s object instantiation code.

Subclass – a more specific version of a class. A Cat is a more specific type of Pet, so you could say that Cat is a subclass of Pet.

Polymorphism – when a subclass changes things from its superclass but still has the same method names and whatnot, it’s referred to as polymorphism.

Let’s say there’s a Pet class. It’s an abstract class, meaning it can’t be instantiated on its own. Instead, there are subclasses, Cat, Dog, and Fish.

The Pet constructor requires a string argument to set the name. Then it sets its instance variable to the name provided when the object is created and initialized. The Pet class also has a method called greet(), but each subclass overrides that method. So they all use the greet() method, but they exhibit different behavior, as seen in the following code example.

Pet polymorphicExample1 = new Cat(“Fluffy”);

Pet polymorphicExample2 = new Dog(“Benny”);

Pet polymorphicExample3 = new Fish(“Bubbles”);

polymorphicExample1.greet();

Output: “meow”

polymorphicExample2.greet();

Output: “woof woof”

polymorphicExample3.greet();

Output: “blub blub”

In a subclass constructor, you typically want to invoke the superclass constructor first. In Java, the superclass constructor is called with super() from within the subclass constructor. If you don’t explicitly do it, it might be implicitly done, but there can be problems with that. In Java, the implicit super constructor invocation will contain no arguments. In the examples for Cat, Dog, and Fish, they would need to call the superclass constructor with the string argument passed to it. If a superclass constructor requires arguments, the subclass constructor must provide them to it, unless the superclass constructor is overloaded and allows for instantiation with no arguments and instead provides default initialization to its instance variables.

You can have any generic thing refer to a more specific thing.

Cat myPet = new Cat(“name”);

You can also do this:

Pet myPet = new Cat(“name”);

If you want to have an array of different kinds of pets, you can have a Pet array, and then put more specific subclasses in the indices so that you can have an array with Cat objects, Dog objects, and Fish objects.

Deep vs. shallow copy – let’s say you make a class called MyCoolClass, and it has many different variables and functions associated with it. It takes three arguments with the constructor: an integer for a, an integer for b, and an integer for c. The constructor then initializes the instance variables with the given values.

Then you make an instance of the class:

MyCoolClass exampleInstance = new MyCoolClass();

Then you want to copy it. But what does copying mean exactly?

Well, let’s start with what you might think would work:

MyCoolClass copiedInstance = exampleInstance;

But that doesn’t actually copy it! It merely makes something an identifier that points to the same object that the exampleInstance identifier points to. In other words, there are two references for a single thing.

Having two references to the same thing means any changes made with one will be reflected in the other, which might not be what you want.

If you want there to be deep copying, you could manually do it, like this:

MyCoolClass copiedInstance = new MyCoolClass(copiedInstance.getA(), copiedInstance.getB(), copiedInstance.getC());

But that’s not the best way to do it, especially if you want to do much copying. Using all of these method calls can be error-prone, and it makes your code look messier. So something you might want to do is create copy constructors. Then, you really will be able to copy something and have it be a deep copy.

A copy constructor takes an instance of its own kind of class as an argument. For a MyCoolClass copy constructor, it would take a MyCoolClass object as an argument, and then set its own instance variables (using the “this” keyword) to the values of the instance variables of the object passed in as an argument, thereby copying the object deeply. The following is a MyCoolClass class copy constructor:

public MyCoolClass(MyCoolClass thingToCopy) {

this.a = thingToCopy.getA();

this.b = thingToCopy.getB();

this.c = thingToCopy.getC();

}

When you have a properly-written copy constructor, you will be able to make two separate copies when you do something like this:

MyCoolClass copiedInstance = exampleInstance;

← Previous | Next →

Intermediate CS Topic List

Main Topic List

Leave a Reply

Your email address will not be published. Required fields are marked *