Lecture 04 - Encapsulation
Lecture 04 - Encapsulation
6 Framework
Chapter 5 – Understanding
Encapsulation
Outline
• Introduction to C# class type
• Understanding constructors
• The role of the this keyword
• Understanding the static keyword
• C# access modifiers
• The first pillar of OOP
• Understanding encapsulation services
• Understanding object initialization syntax
• Understanding partial classes
Introducing the Class Type
• A class is a user-defined type that is composed of
– field data (often called member variables)
– members that operate on this data (such as constructors,
properties, methods, events, and so forth).
• The set of field data represents
– the "state" of a class instance (otherwise known as an
object).
• The power of object-oriented languages is that
– we can model software after real world entities
– by grouping data and related functionality in class
definition
Activity:
• Create new Console Application named
SimpleClassExample
• Insert a new class file named Car.cs
– Add Class > Choose Class Icon
Class Car
• A class is defined in C# using class keyword
– Here is the simplest class definition
class Car{
}
• Next are the members to represent the state
– Here is the examples of the states
class Car{
//The ‘state’ of the Car.
public string petName;
public int currSpeed;
}
Class Car
• Next are the members to model behaviors
– Method to SpeedUp() and anther PrintState()
using System;
namespace SimpleClassExample
{
public class Car
{
//The 'state' of the Car.
public string petName;
public int currSpeed;
//The functionality of the Car
public void PrintState(){
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta){
currSpeed += delta;
}
}
}
The Program class
using System;
namespace SimpleClassExample
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("************ Fun with Class Types ************");
//Allocate and configure a Car object
Car myCar = new Car();
myCar.petName = "Henry";
myCar.currSpeed = 10;
//Speed up the car a few times and print out th state
for (int i = 0; i <= 10; i++)
{
myCar.SpeedUp(5);
myCar.PrintState();
}
Console.ReadLine();
}
}
}
Understanding the constructor
• Objects have state, we want to assign relevant
values to the object's field data before use
– E.g., a Car class demands that the petName and
currSpeed fields be assigned on a field-by-field basis
• Constructors allow the state of an object to be
established at the time of creation.
– It is a special method of a class
– It is called indirectly when creating an object
– It never has a return value (not even void)
– It is always named identically to the class name
The Role of the Default Constructor
• Every class is provided with default
constructor that you can redefine if needed
– A default constructor never takes arguments
– It ensures all field data is set to an default value
• If you are not satisfied with these default
assignments
– You may redefine the default constructor to suit
your needs
Example of default constructor
public class Car
{
//The 'state' of the Car.
public string petName;
public int currSpeed;
//A custom default constructor
public Car(){
petName = "Chuck";
currSpeed = 10;
}
…
}
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("************ Fun with Class Types ************");
//Allocate and configure a Car object
Car myCar = new Car();
myCar.PrintState();
Console.ReadLine();
}
}
Defining Custom Constructors
• We can define additional constructors beyond
the default
– To provide a simple/consistent way to initialize the
object state directly at the time of creation
Defining Custom Constructors
using System;
namespace SimpleClassExample
{
public class Car
{
//The 'state' of the Car.
public string petName;
public int currSpeed;
//A custom default constructor
public Car()
{
petName = "Chuck";
currSpeed = 10;
}
public Car(string pn)//currSpeed will get default value is 0.
{
petName = pn;
}
public Car(string pn, int cs)//Set the full state of the car
{
petName = pn;
currSpeed = cs;
}
public void PrintState()
{
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta){
currSpeed += delta;
}
}
}
Defining Custom Constructors
using System;
namespace SimpleClassExample
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("************ Fun with Class Types ************");
Car chuck = new Car();//Default constructor
chuck.PrintState();
Car mary = new Car("Mary");//Constructor with petName initialization
mary.PrintState();
Car daisy = new Car("Daisy", 75);//Constructor with both data fields
daisy.PrintState();
}
}
}
The Default Constructor Revisited
• Try insert a new class MotorCycle and test it
using System;
namespace SimpleClassExample
{
public class Motorcycle
{
public void PopAWheely(){
Console.WriteLine("YeeeeeeeHaaaaeeewww!");
}
}
}
using System;
namespace SimpleClassExample
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("************ Fun with Class Types ************");
Motorcycle mc = new Motorcycle();
mc.PopAWheely();;
}
}
}
The Default Constructor Revisited
• If you do not define a custom constructor
– The compiler grants you a default
– To allow to allocate an instance with default values
• However, when you define a unique constructor
– The compiler assumes you have taken matters into your
own hands
• You must explicitly redefine the default constructor
– If you want to allow the object user to create an instance
with the default constructor, as well as your custom
constructor
– Vast majority of cases, the default constructor is
intentionally empty (to set all to default values)
The Default Constructor Revisited
using System;
class Motorcycle
{
public int driverIntensity;
Program.cs
// Make a Motorcycle with a rider named Tiny?
Motorcycle c = new Motorcycle(5);
c.SetDriverName("Tiny");
c.PopAWheely();
Console.WriteLine("Rider name is {0}", c.name); // Prints an empty name value!
Chaining constructor calls using this
• this keyword can be used to is to design a class using a technique
termed constructor chaining
– This design pattern is helpful when you have a class that defines
multiple constructors
– To re-use of the processing logic in another constructor (e.g.,
validation)
• An approach is
– Designate the constructor that takes the greatest number of
arguments as the "master constructor"
– Have its implementation perform the required validation logic
– Other constructors can make use of the this keyword to forward the
incoming arguments to the master constructor
– In this way, you need to worry only about maintaining a single
constructor for the entire class, while the others are basically empty
Chaining constructor calls using this
class Motorcycle
{
public int driverIntensity;
public string driverName;
public Motorcycle() { }
// Redundent constructor logic!
public Motorcycle(int intensity){
if (intensity > 10){
intensity = 10;
}
driverIntensity = intensity;
}
// Constructors.
public Motorcycle() { }
// Constructor chaining.
public Motorcycle() { }
public Motorcycle(int intensity)
: this(intensity, "") { }
public Motorcycle(string name)
: this(0, name) { }
// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Understanding the static keyword
• A class may define any number of static
members using static keyword
– The member must be invoked directly from the
class level, not from an object reference variable
– E.g.,
Console c = new Console();
c.WriteLine("I can't be printed...");
– But instead simply use the class name
Console.WriteLine("Much better! Thanks...");
Understanding the static keyword
• Simply put, static members are
– Items that are deemed (by the class designer) to be so
commonplace
– So there is no need to create an instance of the class
before invoking the member
• While any class can define static members, they are
quite commonly found within utility classes
– By definition, a utility class is a class that does not maintain
any object-level state and is not created with the new
keyword
– Rather, a utility class exposes all functionality as class-level
members
– Many .NET base class libraries are built this way
Understanding the static keyword
• The static keyword can be applied to the
following
– Data of a class
– Methods of a class
– Properties of a class (will study)
– A constructor
– The entire class definition
Defining Static Field Data
• Instance level data (nonstatic data)
– The object maintains its own independent copy of
the data
• Static level data (static data)
– The memory is shared by all objects of that
category
Activity: Saving Account
• Create a new Console Application project named
StaticDataAndMembers
• Insert a new class into your project named
SavingsAccount
• Defining
– An instance-level data (to model the current balance)
– A custom constructor to set the initial balance
– A static data named currInterestRate (default: 0.04)
Activity: Saving Account
using System;
namespace StaticDataAndMembers
{
// A simple savings account class.
class SavingsAccount
{
// Instance-level data.
public double currBalance;
namespace StaticDataAndMembers
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
SavingsAccount s3 = new SavingsAccount(10000.75);
Console.ReadLine();
}
}
}
Activity: Saving Account
This is allocated
once and shared
among all
instances
If the interest rate is modeled instance variable, this would mean every SavingsAccount
object would have its own copy of the currInterestRate field.
Now, assume you created 100 SavingsAccount objects and needed to change the interest
rate. That would require you to call the SetInterestRate() method 100 times!
Defining Static Methods
• It’s compiler error to access static member
using instance
• Let's update the SavingsAccount class to
define two static methods
– The first static method (GetInterestRate()) will
return the current interest rate
– The second static method (SetInterestRate()) will
allow you to change the interest rate.
Defining Static Methods
using System;
namespace StaticDataAndMembers
{
// A simple savings account class.
class SavingsAccount
{
// Instance-level data.
public double currBalance;
namespace StaticDataAndMembers
{
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
// Make new object, this does NOT 'reset' the interest rate.
SavingsAccount s3 = new SavingsAccount(10000.75);
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
}
}
}
Defining Static Constructors
• A typical constructor
– Is used to set the value of an object's instance-
level data at the time of creation
– If we assign the value of a static data in a normal
constructor? The value is reset each time you
create a new object
Defining Static Constructors
using System;
namespace StaticDataAndMembers
{
// A simple savings account class.
class SavingsAccount
{
// Instance-level data.
public double currBalance;
namespace StaticDataAndMembers
{
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
// Make an account.
SavingsAccount s1 = new SavingsAccount(50);
// A static constructor!
static SavingsAccount()
{
Console.WriteLine("In static ctor!");
currInterestRate = 0.04;
}
namespace StaticDataAndMembers
{
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
// Make new object, this does NOT 'reset' the interest rate.
SavingsAccount s3 = new SavingsAccount(10000.75);
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
}
}
}
Defining Static Constructors
• Here are a few points of interest regarding static
constructors:
– A given class may define only a single static constructor. In
other words, the static constructor cannot be overloaded.
– A static constructor does not take an access modifier and
cannot take any parameters.
– A static constructor executes exactly one time, regardless
of how many objects of the type are created.
– The runtime invokes the static constructor when it creates
an instance of the class or before accessing the first static
member invoked by the caller.
– The static constructor executes before any instance-level
constructors.
INTRODUCING PILLARS OF OOP
Pillars of OOP
• There are three core principles of OOP, often
called the pillars of OOP
– Encapsulation: How does this language hide an
object's internal implementation details and
preserve data integrity?
– Inheritance: How does this language promote
code reuse?
– Polymorphism: How does this language let you
treat related objects in a similar way?
The Role of Encapsulation
• Encapsulation is about the ability to
– Hide unnecessary implementation details from the object user
– Protect data from direct access from outsider
• Ability to hide implementation details
– To keeps coding tasks simpler, no need to worry about the codes
behind the scenes
– For example, you can use Console.WriteLine(); without needing
to know the codes inside it
• Data protection
– Ideally, an object's state data should be specified using the
private (or possibly protected) keyword
– Outside world must access to the underlying value under a
controlled manner
The Role of Inheritance
• It is the ability to allow you to build new class
definitions based on existing class
– It allows to extend the behaviors/characteristics of a
base (or parent) class by inheriting these into the
derived subclass (child class)
Code Reusability Revisited
• There is another form of code reuse in the world of OOP:
– the containment/delegation model also known as the "has-a"
relationship or aggregation.
– This form of reuse is not used to establish parent-child
relationships.
– The "has-a" relationship allows one class to define a member
variable of another class and expose its functionality (if
required) to the object user indirectly
• For example, a car "has-a" radio
– It would be illogical to attempt to derive the Car class from a
Radio, or vice versa (a Car "is-a" Radio? I think not!)
– Rather, you have two independent classes working together,
where the Car class creates and exposes the Radio's
functionality
The Role of Polymorphism
• It is ability to treat related objects in a similar manner
– It allows a base class to define a set of members (formally termed the
polymorphic interface) that are available to all descendants
– A class's polymorphic interface is constructed using any number of
virtual or abstract members (will study later)
• A virtual member
– It is a member in a base class that defines a default implementation
that may be changed (or more formally speaking, overridden) by a
derived class
• An abstract method is a member
– It is a member in a base class that does not provide a default
implementation but does provide a signature.
– When a class derives from a base class defining an abstract method, it
must be overridden by a derived type
The Role of Polymorphism
ACCESS MODIFIERS
C# Access Modifiers
• When working with encapsulation
– You must always take into account which aspects
of a type are visible
– To control how "visible" the item is to other parts
of your application we use access modifiers
– Although C# defines numerous keywords to
control access, they differ on where they can be
successfully applied (type or member)
C# Access Modifiers
C# Access Modifier May Be Applied To Meaning in Life
public Types or type members Public items have no access restrictions. A public member can be accessed from
an object, as well as any derived class. A public type can be accessed from other
external assemblies.
private Type members or nested types Private items can be accessed only by the class (or structure) that defines the
item.
protected Type members or nested types Protected items can be used by the class that defines it and any child class.
However, protected items cannot be accessed from the outside world using the
C# dot operator.
internal Types or type members Internal items are accessible only within the current assembly. Therefore, if you
define a set of internal types within a .NET class library, other assemblies are not
able to use them.
protected internal Type members or nested types When the protected and internal keywords are combined on an item, the
item is accessible within the defining assembly, within the defining class, and by
derived classes.
The Default Access Modifiers
• By default
– Type members are implicitly private while types are
implicitly internal.
// An internal class with a private default constructor.
class Radio
{
Radio(){}
}
– This is the same as
// An internal class with a private default constructor.
internal class Radio
{
private Radio(){}
}
Access Modifiers and Nested Types
• Nested type
– A type declared directly within the scope of class or structure
– The private, protected, and protected internal access modifiers can be
applied to a nested type
– Example
public class SportsCar{
// OK! Nested types can be marked private.
private enum CarColor{
Red, Green, Blue
}
}
• However, nonnested types can be defined only with the public or
internal modifiers
– The following class definition is illegal:
// Error! Nonnested types cannot be marked private!
private class SportsCar{}
ENCAPSULATION SERVICES
Encapsulation Services
• An object's data should not be directly accessible from
an object instance.
– Class data is defined as private
– This is accessible indirectly using public members
• The problem with public data is that
– The data itself has no ability to "understand" whether the
value to assigned to it is valid
• Private data could be indirectly manipulated using one
of two main techniques
– You can define a pair of public accessor (get) and mutator
(set) methods
– You can define a public .NET property
Encapsulation Using Traditional
Accessors and Mutators
• We will use a case study to demonstrate this
– Create a new project named EmployeeApp
– Add a class named Employee
Employee.cs
using System;
namespace EmployeeApp{
class Employee{
// Field data.
private string empName;
private int empID;
private float currPay;
// Constructors.
public Employee() { }
public Employee(string name, int id, float pay){
empName = name;
empID = id;
currPay = pay;
}
// Methods.
public void GiveBonus(float amount)
{
currPay += amount;
}
public void DisplayStats()
{
Console.WriteLine("Name: {0}", empName);
Console.WriteLine("ID: {0}", empID);
Console.WriteLine("Pay: {0}", currPay);
}
}
}
Cannot access private member from
outside
• The empName, empID, and currPay fields are not directly accessible from
an object variable
– Therefore, the following logic in Main() would result in compiler errors:
using System;
namespace EmployeeApp
{
class MainClass
{
static void Main(string[] args)
{
Employee emp = new Employee();
// Constructors.
public Employee() { }
public Employee(string name, int id, float pay)
{
empName = name;
empID = id;
currPay = pay;
}
// Accessor (get method).
public string GetName()
{
return empName;
}
This technique requires two uniquely named methods to operate on a single data point
Accessor & Mutator Approach
using System;
namespace EmployeeApp
{
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Encapsulation *****\n");
// Longer than 15 characters! Error will print to console.
Employee emp2 = new Employee();
emp2.SetName("Xena the warrior princess");
Console.ReadLine();
}
}
}
.NET Properties Approach
• .NET languages prefer to enforce data
encapsulation state data using properties
– Properties are just a simplification for "real"
accessor and mutator methods
– You are still able to perform any internal logic
.NET Properties Approach
using System;
namespace EmployeeApp
{
class Employee
{
// Field data.
private string empName;
private int empID;
private float currPay;
// Properties!
public string Name
{
get { return empName; }
set
{
if (value.Length > 15)
Console.WriteLine("Error! Name length exceeds 15 characters!");
else
empName = value;
}
}
.NET Properties Approach
// We could add additional business rules to the sets of these properties;
// however, there is no need to do so for this example.
public int ID
{
get { return empID; }
set { empID = value; }
}
public float Pay
{
get { return currPay; }
set { currPay = value; }
}
// Methods.
public void GiveBonus(float amount)
{
currPay += amount;
}
public void DisplayStats()
{
Console.WriteLine("Name: {0}", empName);
Console.WriteLine("ID: {0}", empID);
Console.WriteLine("Pay: {0}", currPay);
}
}
}
.NET Properties Approach
• Properties also are easier to manipulate
– Properties are able to respond to the intrinsic
operators of C#
• E.g., to add 100 to the current pay
– Using accessor, mutator:
• emp.setPay(emp.getPay() + 100);
– Using properties:
• emp.Pay += 100;
Using Properties Within a Class
Definition
• Now, every place within the class that you
would like to get/set the data
– You should always use the Properties
– To make sure that the validation/access controls
code are executed in not only outside but also
within the class
Read-Only and Write-Only Properties
• Read/write only properties
– To configure read-only property just omit the set
– To configure write-only property just omit the get
• Example
public string SocialSecurityNumber
{
get { return empSSN; }
}
Understanding Automatic Properties
• In some cases you may only need for simple getting and setting the value
– It can be trivial to define private backing fields and simple property definitions multiple times
– You may use automatic property syntax
class Car
{
public string PetName { get; set; }
}
• At compile time, C# compiler will provide
– an autogenerated private backing field
– a fitting implementation of the get/set logic
• With the current version of C#
– it is now possible to define a "read-only automatic property" by omitting the set scope
– It is not possible to define a write-only property
• The class defining automatic properties will always need to use property syntax to
get and set the underlying value
– Why?
– What is good about this?
Initialization of Automatic Properties
class Garage
{
// The hidden backing field is set to 1.
public int NumberOfCars { get; set; } = 1;
public Garage() { }
public Garage(Car car, int number)
{
MyAuto = car;
NumberOfCars = number;
}
}
Understanding Partial Classes
// Employee.cs
partial class Employee
{
// Methods
// Constructors
// Methods
// Employee.Core.cs
// Properties
partial class Employee
}
{
// Field data
// Constructors
}
Summaries
• Introduction to C# class type
• Understanding constructors
• The role o the this keyword
• Understanding the static keyword
• C# access modifiers
• The first pillars of OOP
• Understanding encapsulation services
• Understanding object initialization syntax
• Understanding partial classes
References
Troelsen, A. & Japikse, P., 2015. C# 6 and the
.NET 4.6 Framework. 7th ed. Apress.