Skip to content
UoL CS Notes

COMP282 (Advanced Object-Oriented C Languages)

C# Design Patterns (MVC, Factory & Decorator)

COMP282 Lectures

Model-View-Controller

This is a design pattern that splits a user interface into three parts:

  • Model - The internal state and data of the application.
  • View - The GUI elements and how they appear to the user.
  • Controller - Create events that modify the internal state of the application.

MVC Advantages

  • Different parts can be done by different people.
  • High cohesion.
  • Low coupling
  • Easier to maintain.
  • Greater code reuse.

MVC Disadvantages

  • Mode code and higher levels of abstraction.
  • Easier to forget to do a part because the parts are at different places.

Factories

Consider that you have a set of classes and you want to create one depending on an input.

  • We have a set of animal classes and want to create an animal.

We can use the following as a solution:

  1. Give the classes a common interface.
  2. Create a new class named <ClassName>Factory.
  3. This class has a single method, get<ClassName>(), that takes a class and produces a new instance of that class.

Decorators

This pattern can solve the following problems:

  • You want to add a responsibility to an object at run time.
  • You want to do sub-classes but with multiple choice.

We can use the following as a solution:

  1. Create an interface for the class <ClassName> and call it I<Classname>.
  2. Create an <ClassName>Decorator class, implementing I<ClassName> and having an I<ClassName> field.
  3. The constructor takes an I<ClassName> element and puts it into the field.
  4. All methods are just called on that field.
  5. We can then create subclasses of <ClassName>Decorator that will overwrite methods and then do some operations before calling methods on the field.

We can implement this in the following example:

  1. Consider that we are making a text editor that has a Text object. We can implement an IText interface that allows us to define a draw method.
  2. We can make a class TextDecorator that implements IText with an IText field.
  3. TextDecorator has an IText field and takes IText in the constructor.
  4. We can then create subclasses of TextDecorator and use them like so:

     TextFirst(new TextUpper(text));
    

Rule of Three

This rule states that we should wait until we have three of something before we generalise using a design pattern.

  • This saves us putting in extra effort in generalising when we have no need, or too many classes.

Introduction to C#

COMP282 Lectures

C# is a fully managed language (like Java). Memory is garbage collected and the compiler handles the allocation of resources automatically.

Hello, world!

A hello world program would look something like so:

using System;
class Hello {
	static void Main(string[] args) {
		Console.WriteLine("Hello World!");
	}
}

Getters & Setters in C#

Getters and setters are used like so in C#:

class Person {
	private string _name;
	public string Name {
		get {
			return _name;
		}
		set {
			_name = value;
		}
	}
}
class Program {
	public static void Main(string [] args) {
		Person p = new Person();
		p.Name = "Charles";
		System.Console.WriteLine(p.Name);
	}
}

It is convention that private variables start with _.

private Getters & Setters

Using one of these outside of the scope of the class will result in a compilation error:

class Person {
	private string _name;
	public string Name {
		private get {
			return _name;
		}
		private set {
			_name = value;
		}
	}
}

Interfaces in C#

C# has interfaces like in Java. We can make and use them like so:

using System;
class Program : IBla {
	static void Main(string[] args) {
		Console.WriteLine("Hello World!");
	}
}
interface IBla {
	bool test();
}

Inheritance in C#

Inheritance in C# is similar to in C++:

using System;
class Program : Bla {
	static void Main(string[] args) {
		Console.WriteLine("Hello World!");
	}
}
class Bla {
	public bool test() {
		return true;
	}
}

Constructors in C#

Constructors look like so in C#:

using System;
class Program : Bla {
	public Program(int b) : base(b) {}
	public Program() : this(0) {}
}
class Bla {
	public Bla(int a) {}
}
  • base is used to refer to the class we inherit (Bla).
  • this is used to refer to the current class (Program).

Inheritance & Interfaces in C#

We can declare both interfaces and inheritance at once:

using System;
class Program : Bla, IBla {
	static void Main(string[] args) {
		Console.WriteLine("Hello World!");
	}	
}
interface IBla {
	bool test();
}
class Bla {
	public bool test() {
		return true;
	}
}

The class must be listed before any interfaces.

Polymorphism in C#

Polymorphism is similar to C++:

using System;
class Program : Bla {
	public override bool test() {
		return false;
	}
}
class Bla {
	public virtual bool test() {
		return true;
	}
}

Our methods can be set in three ways:

  • Using both virtual and override:
    • This will override as expected.
  • Without virtual or override:
    • This will take the function of the parent.
  • With both abstract:
    • This works similarly to in Java.

Access Modifiers in C#

We have the standard access modifiers:

  • public
  • private
  • protected

We also have access modifiers that are used to inform that creation of .dll files:

  • internal - Only from the same compilation.
    • If you compile to a .dll or .exe file, you can only access these from .cs files used to compile it.
  • protected or internal - From the same compilation or from children.
  • protected internal - From children in the same compilation.

Overloading in C#

Generally people don’t overload operators - instead of overloading + we make use add(). This will be shown anyway.

Requirements:

  • Operator overloading must be defined in a class that defines one of the parameters.
  • It must be public and static (meaning call-able from outside an object).
using System;
class Fraction {
	int n;
	int d;
	public Fraction(int n, int d) {
		this.n = n;
		this.d = d;
	}
	public static Fraction operator +(Fraction a) {
		return a;
	}
	public static Fraction operator +(Fraction a, Fraction b) {
		return new Fraction(a.d*b.n + b.d*a.n,a.d*b.d);
	}
}

If we overload true we must also overload false.

Templates (Generics) in C#

We can use generics like so:

class Pair<T,U> {
	public T first { get; private set; }
	public U second { get; private set; }
	public Pair(T t, U u) {
		first = t;
		second = u;
	}
}

File $\rightarrow$ Class Correspondence

In C# you can have any number of files per class and any number of classes per file.

It is preferred to still have one class per file.

To use multiple flies for one class, you need to add the keyword partial in front of class.

Types

All types in C# are objects we can use int and string as shorthand for their full class name.

You can see the full list of types here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types.

STL in C#

In C# we have many collection types:

  • Array
  • List
  • SortedDictionary
  • SortedSet
  • LinkedList

foreach

We can iterate over all the items in a collection with the following structure:

foreach (var x in xs)

LINQ

LINQ is a query language in C#. We can use is like so:

using System;
using System.Linq;
using System.Collections.Generic;
class Program {
	public static void Main(string[] args) {
		string[] names = { "Birgit", "Brian", "Victor", "Finn", "Denise" };
		IEnumerable<string> bnames = //query variable
			from name in names //required
			where name.Substring(0,1)=="B" // optional
			orderby name descending // optional
			select name; //must end with select or group
			foreach (var name in bnames) Console.WriteLine(name);
	}
}

Max() in C#

We can calculate a maximum value using LINQ:

using System;
using System.Linq;
using System.Collections.Generic;
class Program {
	public static void Main(string[] args) {
		string[] names = { "Birgit", "Brian", "Victor", "Finn", "Denise" };
		Console.WriteLine(names.Max());
	}
}

Distinct() in C#

using System;
using System.Linq;
using System.Collections.Generic;
class Program {
	public static void Main(string[] args) {
		string[] names = { "Birgit", "Brian", "Victor", "Finn", "Denise" };
		foreach (var name in names.Distinct()) Console.WriteLine(name);
	}
}

Shuffle with orderby

We can implement a shuffle by sorting at random:

using System;
using System.Linq;
using System.Collections.Generic;
class Program {
	public static void Main(string[] args) {
		string[] names = { "Birgit", "Brian", "Victor", "Finn", "Denise" };
		Random rng = new Random();
		IEnumerable<string> scoreQuery = //query variable
			from name in names //required
			orderby rng.Next() // optional
			select name; //must end with select or group
			foreach (var testScore in scoreQuery) Console.WriteLine(testScore);
	}
}

Standard Template Library - Algorithms

COMP282 Lectures

There are the following types of algorithms:

  • Min/Max:
    • min_element, max_element
  • Sorting:
    • sort, stable_sort
  • Non-modifying Sequence:
    • all_of, any_of, none_of, find_if, count_if
  • Modifying Sequence:
    • copy, shuffle, unique, reverse
  • Misc
    • Helper functions for searches, sorts and tree structures.

min

We can write our own simple algorithm like so:

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

bool compare(int i1, int i2) {
	return i1 < i2;
}

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++) {
		arr.push_back(i);
	}
	cout << *min_element(arr.begin(), arr.end(), compare);
}

This functionality is also available in functional:

#include <vector>
#include <algorithm>
#include <funcitonal>
#include <iostream>
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++) {
		arr.push_back(i);
	}
	cout << *min_element(arr.begin(), arr.end(), less<int>());
}

Functors

Functors are classes with () overloaded, like less. We can use them instead of pointers.

Comparator.hpp:

class Comparator {
	int c;
	public:
	Comparator(int c) : c(c) {}
	bool operator()(int i1, int i2);
}

Comparator.cpp:

#include "Comparator.h"
#include <cmath>
bool Comparator::operator()(int i1, int i2) {
	if (std::abs(c - i1) < std::abs(c - i2)) {
		return true;
	}
	return false;
}

We can then use the comparator in our min program:

#include <vector>
#include <algorithm>
#include <funcitonal>
#include <iostream>
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++) {
		arr.push_back(i);
	}
	int c;
	cin >> c;
	cout << *min_element(arr.begin(), arr.end(), Comparator(c));
}

sort & stable_sort

#include <vector>
#include <algorithm>
#include <iostream>
#include Comparator.h
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++ ) {
		arr.push_back(i);
	}
	int c;
	cin >> c;
	sort(arr.begin(), arr.end(), Comparator(c));
	for (auto x : arr) {
		cout << x << " ";
	}
}

We can replace sort with stable_sort if we want to sort stably.

shuffle

#include <vector>
#include <algorithm>
#include <iostream>
#include <random>
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++ ) {
		arr.push_back(i);
	}
	unsigned seed;
	cin >> seed;	// we can also set the seed using the time
	shuffle(arr.begin(), arr.end(), default_random_engine(seed));
	for (auto x : arr) {
		cout << x << " ";
	}
}

unique

This algorithm uses == to remove duplicates:

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++ ) {
		arr.push_back(1);
	}
	int c;
	cin >> c;
	unique(arr.begin(), arr.end());
	for (auto x : arr) {
		cout << x << " ";
	}
}

copy

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int main() {
	vector<int> arr;
	for (int i = 0; i < 100; i++ ) {
		arr.push_back(i);
	}
	vector<int> arr2;
	for (int i = 0; i < 100; i++ ) {
		arr2.push_back(-i);
	}
	move(arr.begin() + 5, arr.end(), arr2.begin() + 5);
	for (auto x : arr2) {
		cout << x << " ";
	}
	cout << endl;
	cout << arr.size();
	for (auto x : arr) {
		cout << x << " ";
	}
}

Standard Template Library - Containers & Iterators

COMP282 Lectures

Standard Template Library

Each template consists of three main parts:

  • Containers - Standard data structures.
  • Iterators - Standard ways to move through the standard data structures.
  • Algorithms - Use iterators to complete a task with data.

Containers

An example of a container is a vector this is how you would initialise a vector:

#include <vector>
using namespace std;
int main() {
	vector<int> store;
}

A vector is an automatically sizing array.

You can use the algorithms of a container using dot noation: store.back().

Iterators

Iterators point to an object in an array, or container. They can iterate through the objects in a logical way, by using overloaded operators such as ++.

See https://www.cplusplus.com/reference/iterator/ for the properties of each iterator category. We can use the functions at the bottom of the page to control the iterator.

If a type is well-defined then we can replace the standard iterator declaration:

multiset<int>::iterator it = store.begin();

with either of the following:

auto it = store.begin();
for (int x : store) { 
	cout << x;
}
for (auto x : store) {
	cout << x;
}

STL Arrays

There is also an array container, in C++, that enable the use of iterators and algorithms:

#include <array>
#include <iostream>
using namespace std;
int main() {
	array<long, 5> arr;
	arr.fill(1); // all elements are 1
	for (auto x : arr) {
		cout << x;
	}
}

C++ Overloading Continued & Templates

COMP282 Lectures

Overloading

Types of Operators

There are three types of operator in C++:

  • Prefix Unary - --counter
  • Postfix Unary - counter++
  • Binary - num1 - num2

Unary Operators

To overload prefix unary operators we write:

returntype operator-();

This will override -.

To overload postfix unary operators we write:

returntype operator++(int);

The int type only means that we are defining the postfix version instead of the prefix one.

Binary Operators

To overload a binary operator we can do the following:

returntype operator+(righthandtype& r);

Friends

A friends declaration appears in a class body and grants a function, or another class, access to private and protected members of the class where the friend declaration appears.

Friendship is only one-way and is not inherited.

Redefining << for cout

To print our ruler objects we need to override << from ostream so that it knows how to output the type.

To start define the function in the class:

friend std::ostream& operator<<(std:ostream& output, const Ruler& ru);

We can then define that function elsewhere:

std::ostream& operator<<(std::ostream& output, const Ruler& ru) {
	output << ru.feet << "'" << ru.inches << '"';
	return output;
}

We can do a similar thing for overriding >>.

There will be additional examples of this in the labs.

Templates

Consider that we want to write a function to compare many different types of classes. Instead of writing many functions to do this, we can write one function to rule them all:

template <class T>
bool compare(T& t1, T& t2) {
	return t1 < t2;
}

We can then use this template like so:

compare<Ruler>(r1, r2);
compare<string>(s1, s2);

You can also make templates that take multiple classes:

template <class T, class U>
bool compare(T& t1, U& u2) {
	return t1 < u2;
}

Class Templates

To return two different objects from a single function we can use a class template:

template <class T, class U>
class TwoObjects {
	public
	T first;
	U second;
	TwoObjects(T f, U s) : first(f), second(s) {}
}
TwoObjects<Animal, int> method();

This is implemented by Pair in the standard template library.

C++ Inheritance & Overloading

COMP282 Lectures

Inheritance

  • c++ has access modifiers on inheritance.
  • There are no interfaces like in Java.

Basic inheritance has the following syntax:

class Dog : public Animal();

which represents the following:

classDiagram
Animal <|-- Dog

The access modifiers change the access modifiers of inherited methods etc. public makes no change.

Polymorphism

#include <iostream>
using namespace std;
class Animal {
	public:
	void makeNoise() { cout << "Grunt"; }
};
class Dog : public Animal {
	public:
	void makeNoise() { cout << "Woof"; }
};
int main()
{
	Dog rover;
	Animal* pet = &rover;
	pet->makeNoise();
}

This will make the Grunt noise of Animal.

Virtual Polymorphism

We can override methods properly like so:

#include <iostream>
using namespace std;
class Animal {
	public:
	virtual void makeNoise() { cout << "Grunt"; }
};
class Dog : public Animal {
	public:
	void makeNoise() { cout << "Woof"; }
};

The keyword here is virtual.

Initialisation

Memory for the parent class is always initialised before the memory before the child.

To pass data from the child to the parent constructor, we would do the following:

#include <iostream>
using namespace std;
class Animal {
	protected:
	int weight;
	string name;
	public:
	Animal(int w, string n) : weight(w), name(n) {}
	virtual void output() {
	cout << name << " weighs " << weight << endl; }
};
class Dog : public Animal {
	public:
	Dog(int w, string n) : Animal(w,n) {}
};
int main() {
	Dog rover(5, Rover");
}

Slicing

We can turn a child object into it’s parent by doing the following:

class Cat : public Animal {
private:
    int lives;
public:
    Cat(int w, string n, int v) : Animal(w, n) {
        lives = v;
    }
    void output() {
        cout << name << " weighs " << weight << " and has ";
        cout << lives << " lives" << endl;
    }
};
int main() {
    Cat fluffy(5, "Fluffy", 9);
    Animal pet = fluffy;
    pet.output();
}

This will remove any attributes of the child.

Accessing a Child Through the Parent

We can have a parent pointer that points to a child object like so:

int main() {
    Cat grumpy(5, "Grumpy Cat", 9);
    Animal* pet = &grumpy;
    cout << ((Cat*)pet)->getLives(); // This will work fine
}

Passing Object Examples

We have the following methods ob passing objects to functions:

  1. Directly by copying the object:

     void eat(Animal toBeEaten) {
         weight += toBeEaten.weight;
         toBeEaten.weight = 0;
     }
    
  2. Passing a pointer (must use &):

     void eat(Animal* toBeEaten) {
         weight += toBeEaten->weight;
         toBeEaten->weight = 0;
     }
    
  3. Pass by reference:

     void eat(Animal& toBeEaten) {
         weight += toBeEaten.weight;
         toBeEaten.weight = 0;
     }
    

Permanent Objects

To keep objects once we leave their scope we can allocate memory on the heap with the following syntax:

Dog* myDog = new Dog(10, "Rover");
Cat* myCat = new Cat(5, "Grumpy", 9);
  • This creates a new pointer where we will have to use -> to access methods and so on.

To free the memory after we have finished with an object we can delete it:

delete(myDog); // this calls the destructor for Dog

Overloading

  • Two functions can have the same name if they have different parameters.

    This is most often used with constructors.

#include <iostream>
#include "Animal.h"
#include "Dog.h"
using namespace std;
void name(Animal *a) {
    cout << "Animal";
}
void name(Dog *d) {
    cout << "Dog";
}
int main()
{
    Dog rover;
    Animal *pet = &rover;
    name(rover);
    name(pet);
}

Constructor Overloading

We can write the following header file:

class Ruler {
public://public right now to make it easier to demonstrate
    int feet;
    int inches;
public:
    Ruler(int i);
    Ruler(int f, int i);
};

and implement our constructors like so:

#include "Ruler.h"
Ruler::Ruler(int f, int i) {
	feet = f + i / 12
	inches = i % 12;
}
Ruler::Ruler(int i) : Ruler(0,i) {}

Operator Overloading

To add our new measurement with the + operator we can overload the + operator. We declare this as a member function of a class:

Ruler operator+(const Ruler& ru);

and implement this like so:

Ruler Ruler::operator+(const Ruler& ru) {
    Ruler temp(this->feet + ru.feet, this->inches + ru.inches);
    return temp;
}

We can then add Ruler objects using +.

We can also do a similar thing for equality operators.

Classes in C++

COMP282 Lectures

The nice thing about C++ being compatible with C is that you don’t have to use classes if you don’t want to.

Classes

A basic example of a class looks like so:

#include <string>
class Animal {
	int weight;
	std::string name;
};

Remember to include the semicolon ; at the end of classes.

Access Modifiers

We can use the following access modifiers:

  • public - You can access it from anywhere.
  • private - You can only access them from within the class or from friends.
  • protected - You can only access them from this class, sub-classes or from friends.

The default is private.

The syntax is like so:

#include <string>
class Animal {
public:
	int weight;
	std::string name;
};

Getters & Setters

#include <string>
#include <iostream>
using namespace std;
class Animal {
private:
	int weight;
	string name;
public:
	void setWeight(int value) {
		weight = value;
	}
	int getWeight(void) {
		return weight;
	}
};
int main() {
	Animal example; // Creates the object
	example.setWeight(55);
	cout << example.getWeight();
}

Using Headers

We should put the following in header files:

  • Class Definitions
  • Constants

This will split our code like so:

Animal.h:

#include <string>
namespace ani {
	class Animal {
	private:
		int weight;
		std::string name;
	public:
		void setWeight(int value);
		int getWeight(void);
	};
}

You should not using namespace ... in header files.

Animal.cpp

#include "Animal.h"
int ani::Animal::getWeight() {
	return weight
}
void ani::Animal::setWeight(int value) {
	weight = value;
}

main.cpp

#include <iostream>
#include "Animal.h"
using namespace ani;
using namespace std;

int main() {
	Animal example;
	cout << example.getWeight();
}

We can declare multiple namespaces (provided they don’t clash).

Constructors & Destructors

Constructors are used to allocate memory and initialise objects, whereas destructors free the memory. We can extend our header files to allow for this functionality:

Animal.h:

#include <string>
namespace ani {
	class Animal {
	private:
		int weight;
		std::string name;
	public:
		void setWeight(int value);
		int getWeight(void);
		Animal(int value);
		~Animal();
	};
}

Animal.cpp

#include "Animal.h"
int ani::Animal::getWeight() {
	return weight
}
void ani::Animal::setWeight(int value) {
	weight = value;
}
ani::Animal::Animal(int value) {
	weight = value;
}
ani::Animal::~Animal() {} // You can skip this as this does the same as the default

Member Initialisation

To save us manually initialising variables in our constructors, we can use the following syntax:

ani::Animal::Animal(int value) : weight(value) {}

Introduction to C++

COMP282 Lectures

Hello, world!

C is also valid C++ code:

#include <stdio.h>
int main() {
	printf("Hello World!\n");
}

but we can also use C++ features:

#include <iostream>
int main() {
	std::cout << "Hello World!" << std::endl;
}

Header Files

We can use C header files in C++ but there are often C++ versions of the same. For example, we can find the length of a string like so:

#include <string>
#include <iostream>
int main() {
	std::string hello = "hello";
	int x = hello.size();
	std::cout << hello << " is of length " << x;
}

In C you would use strlen().

Namespaces

Namespaces are like packages, for example:

std::cout

means that cout is defined inn the std namespace.

We can set a default namespace like so:

#include <string.h>
#include <iostream>
using namespace std;
int main() {
	string hello = "hello";
	int x = hello.length();
	cout << hello << " is of length " << x;
}

This saves us writing std:: all the time.

Input Syntax

We can use something like the following to read input:

#include <iostream>
int main() {
	std::string bla;
	int num;
	std::cin >> num >> bla;
	std::cout << bla << " " << num;
}

This can work with integers and strings, and reads up to the first space for each variable.