Encapsulation and Information Hiding (2024)

Kathi Fisler

So far, we’ve focused on how to create classes that are amenable tofuture extensions. Today, we look at making code robust, both in howto enable future modifications (as well as extensions) and inprotecting against malicious or unintentional programming errors.

1Code Critique

For this lecture, start with this starter file for a banking service. Critique it: what problems doyou see in this code with regards to future modifications orinformation protection?

  1. Any class that has access to a customer object has the abilityto access or change that customer’s password. In theBankingService class, for example, the login methoddirectly accesses the password to check whether it is valid; thatmethod could just as easily (maliciously!) change the password. Thecontents of the password should never get out of the Customerclass.

    The real problem here is that login should be a method onCustomer, which has the data that the method needs.

  2. A similar concern applies to the balance field inwithdraw, but withdraw illustrates another problem.Imagine that the bank adds more details to accounts (such as overdraftprotection or a withdrawal fee). The BankingService classwould have to keep changing as the notion of Accounts changes,which makes no sense. The BankingService class simply wants away to execute a withdrawal without concern for the detailed structureof Account objects. The withdraw method needs to be amethod on Account, not BankingService.

  3. The BankingService class has written all of its codeover accounts and customers against a fixed data structure (theLinkedList). The dependency is clear in the code for themethods (getBalance, withdraw, and login): eachincludes a for-loop over the list in its implementation.

  4. The dummy return value of 0 in getBalance andwithdraw is awful, because it does not distinguish between avalid answer (an account balance of 0) and an error condition.Picking a dummy value to satisfy the type system is never a goodidea. This program needs a better way of handing errors.

Underlying the first three of these concerns is a goal calledencapsulation. Intuitively, encapsulation is about bundlingdata and code together in order to (1) reduce dependencies of one partof a system on structural details of another, and (2) controlmanipulation of and access to data. This lecture is about recognizingwhere encapsulation is needed and learning how to introduce it intoyour program. The next lecture will address error handling (item 4).

2Encapsulating Knowledge

Problems 1 and 2 are fundamentally failures to keep data and methodson them in the same class. Here, encapsulation is about regulatingaccess to data (for purposes of reading, modifying, or even knowingabout the existence of some data). These problems illustrate why wewant to encapsulate knowledge in programs.

We will fix these problems in two stages: first, we move each methodinto its proper class (and rewrite the BankingService to usethe new methods; second, we protect the data within these classes fromunauthorized access.

2.1Putting Methods in Their Proper Place

Let’s move the withdraw and getBalance methods into theAccount class:

class Account {

int number;

Customer owner;

double balance;

// returns the balance in this account

double getBalance() {

return this.balance;

}

// deducts given amount from account and returns total deduction

// if add account info, no need to edit BankingService

double withdraw(double amt) {

this.balance = this.balance - amt;

return amt;

}

}

Methods like getBalance, which simply return the value offields, are called getters. Many OO books suggest addinggetters (and a corresponding setter method to change the value)on all fields. This guideline is too extreme though—we’ll return toit at the end of the lecture.

The getBalance and withdraw methods in theBankingService class change as follows to use the new methods.Note that neither one now directly accesses the field containing thedata in Account.

double getBalance(int forAcctNum) {

for (Account acct:accounts) {

if (acct.number == forAcctNum)

return acct.getBalance();

}

return 0;

}

double withdraw(int forAcctNum, double amt) {

for (Account acct:accounts) {

if (acct.number == forAcctNum) {

return acct.withdraw(amt);

}

}

return 0;

}

One advantage to having the separate withdraw method in theAccount class is that if the data in an account changes, we canchange the withdrawal computation without affecting other classes.For example, if the bank introduced withdrawal fees, then the amountdeducted from an account would be the amount requested plus the fee.This new code structure lets the BankingService simply ask toperform the withdrawal, leaving the specifics to the Account class.

Next, let’s move login into the Customer class. Theresult is similar.

class Customer {

String name;

int password;

LinkedList<Account> accounts;

// check whether the given password matches the one for this user

// in a real system, this method would return some object with

// info about the customer, not just a string

String tryLogin(int withPwd) {

if (this.password == withPwd)

return "Welcome";

else

return "Try Again";

}

}

class BankingService {

...

String login(String custname, int withPwd) {

for (Customer cust:customers) {

if (cust.name.equals(custname)) {

cust.tryLogin(withPwd);

}

}

return "Oops -- don't know this customer";

}

}

2.2Access Modifiers in Java

Even though we have edited the BankingService to not directlyaccess a customer’s password or the balance in an account, nothing wehave done prevents the BankingService (or a futureextension of it) from doing so. To make this program more robust, wewant to protect the data in the Customer and Accountclasses from direct access or modification from outside classes.Other classes may be able to access or modify these through getters,setters, or other methods, but at least then the programmer providingthose methods has some control over how the access occurs. Thequestion, then, is how to prevent direct access to the fieldsof a class using an "object.field" expression.

Java provides several access modifiers that programmers can puton classes and methods to control which other classes may use them.The modifiers we will consider in this course are:

  • private means the item is only accessible by name insidethe class. If you make a field private, for example, then if youwanted an object from another class to access the field, you wouldneed to provide a method (like a getter) that enables the access.

  • public means every other class or object can access this item.

  • protected means that objects in the current class and all of itssubclasses (and their subclasses) can access this item.

There are some additional ones that you can use when organizing Javacode into larger units called packages; you’ll get to those if youtake Software Engineering.

For our banking application, we want to make all of the fields in allof the classes private. This is a good general rule of thumb, unlessyou have a good reason to do otherwise. In addition, you should markmethods meant to be used by other classes as public.Concretely, the Customer and Account classes now looklike:

class Customer {

private String name;

private int password;

private LinkedList<Account> accounts;

public String tryLogin(int withPwd) {

...

}

}

class Account {

private int number;

private Customer owner;

private double balance;

public double getBalance() {

...

}

public double withdraw(double amt) {

...

}

}

Access modifiers are checked at compile time. Try accessingcust.password in the login method inBankingService with the access modifiers in place to see theerror message that you would get.

Now that we’ve seen access modifiers, we can explain why Java requiresthat methods that implement interfaces are marked public. Thewhole idea of an interface is that it is a guaranteed collection ofmethods on an object. The concept would be meaningless if the methodsrequired by an interface were not public. The fact that you get acompiler error without the public, though, suggests thatpublic is not the default modifier. That is correct. Thedefault modifier is "public within the package" (where package is theconcept you will see in SoftEng for bundling classes into largerunits). This is more restrictive than pure public, so thepublic annotation is required on all methods that implementparts of interfaces.

2.2.1Guidelines on Access Modifiers

Good programming practice recommends the following guidelines:

  • Put access modifiers on every field and method (includingconstructors) in a class.

  • Make all fields private unless another guideline applies.

  • Any method that is visible through an interface must be public.

  • Any method that another class must use should be public.

  • Any field/method whose visibility can be limited to subclassesshould be marked protected.

  • Make constructors in abstract classes protected, so subclassescan invoke them.

  • Make constructors that can’t be used by other classes private.

Note that subclasses cannot make something less visible than in theirsuperclass. So if a class declares a field as public, you cannotextend the class and have the field be private in the extended class.The reasons for this have to do with the inconsistency of accessinformation given that Java can view an object as either a member ofits class or any superclass of its class.

2.3Adapting the Banking Service to Access Modifiers

Now that we’ve made the Customer and Account fieldsprivate, our code doesn’t compile. We had references tocust.name in the login method, for example; those arenot allowed on private fields. To fix the code, we need to put amethod in the customer class (which can access the name). Two optionscome to mind:

  • Put a getName method in Customer to return thename, then replace cust.name with cust.getName().

  • Put a nameMatches method in Customer that takesthe name to compare to and checks whether the names are equal.

The first suggestion is called a "getter" in object-orientedprogramming. Many tutorials will suggest making a getter for allfields. Is this a good idea?

No. Ask yourself why you made the name private in the firstplace. If you wanted to keep people from reading it, a gettercircumvents that decision. If you only wanted to keep people frommodifying it, a getter might make sense, but good OO practice willstill recommend the second approach – a method that performs thecomputation that you actually want to see happen on the data.

If we follow the second approach, our Customer class will look at follows:

class Customer {

private String name;

private int password;

private LinkedList<Account> accounts;

// check whether customer has given name

public boolean nameMatches(String aname) {

return (this.name.equals(aname));

}

// produce message based on whether given password matches

public String tryLogin(int withPwd) {

if (this.password == withPwd)

return "Welcome";

else

return "Try Again";

}

}

with the login method in BankingService updated to:

public String login(String custname, int withPwd) {

for (Customer cust:customers) {

if (cust.nameMatches(custname)) {

cust.tryLogin(withPwd);

}

}

return "Oops -- don't know this customer";

}

3Encapsulating Representation

Now we return to the third problem we cited in our critique of theoriginal code: the BankingService class fixes the assumptionthat accounts and customers should be stored as linked lists. When welooked at data structures, we talked about using interfaces to allowprogrammers to switch from one data structure to another withoutbreaking code. Here is canonical example of code that does NOT enablethis. If the bank grows to lots of customers and wants to switch tousing an AVL tree, for example, it cannot do so easily because themethods in BankingService have been written specifically forLinkedLists (due to the use of the for-loops). Codedesigned for long-term evolution and maintenance (in other words, mostcode in a production environment) should NOT do this.

To fix this, we need to rewrite the code to remove both thefor-loops and the specific references to LinkedList.But how? This involves several steps, described over the next severalsubsections.

3.1Replace Fixed Data Structures with Interfaces

In general, here is how to factor a fixed data structure outof existing code:

  1. Find all variables whose type you want to generalize.

  2. Introduce interfaces for the types of these variables (somevariables may be able to share the same types).

  3. For each place in the current code that relies on the currenttype of the variable, ask yourself what that code is trying to compute(i.e., figure out a purpose statement for it). Invent a method namefor that computation, add it to the interface, and replace theexisting code with a call to the new method.

To make this clearer, let’s apply these steps to ourBankingService program.

  1. Which variables to we want to generalize?: Each ofaccounts and customers.

  2. Choose interface names for the variables: Each of theseis representing a set, so IAccountSet and ICustSet arereasonable choices.

    interface IAccountSet {}

    interface ICustSet {}

    class BankingService {

    private IAccountSet accounts;

    private ICustSet customers;

    ...

    }

  3. For each place in the current code that relies on thecurrent type of the variable, ask yourself what that code is trying tocompute: Let’s take the original getBalance code as an example.

    double getBalance(int forAcctNum) {

    for (Account acct:accounts) {

    if (acct.numMatches(forAcctNum))

    return acct.getBalance();

    }

    return 0;

    }

    The for-loop here locates the account with the given number,then gets the balance from that account. The general purpose of thefor-loop, then, is to find an account by its number. This suggeststhe following method on IAccountSet:

    interface IAccountSet {

    // returns the account whose number matches the given number

    Account findByNumber(int givenNum);

    }

  4. Replace current code on a specific data type with calls tomethods in the new, general, interface: Now, we rewritegetBalance to use findByNumber.

    double getBalance(int forAcctNum) {

    Account acct = findByNumber(forAcctNum);

    return acct.getBalance();

    }

    Note that we have not yet addressed what happens if there is noaccount with the given number in the list. We will return to that inthe next lecture.

Follow similar steps to generalize the withdraw andlogin methods. We leave these as an exercise so you can practice.

3.2Create Concrete Classes that Implement the New Interfaces

Now that we have rewritten BankingService to useIAccountSet and ICustSet, we need classes that implementthese interfaces. Our original code provides an initialimplementation using LinkedList.

class AcctSetList implements IAccountSet {

LinkedList<Account> accounts;

public Account findByNumber(int givenNum) {

for (Account acct:accounts) {

if (acct.numMatches(givenNum))

return acct;

}

return null;//not good -- will fix in next lecture

}

}

With the generalized findByNumber method, it isn’t clear whatto use as the return type if no account has the given number:different methods that call this search method might need differentdefault answers. For now, we will use the very wrong approachof returning null, just so we can get the code to compile. Wewill discuss how to do this properly in the next lecture.

3.3Initialize Data with Objects of the New Concrete Class

We have generalized BankingService and made new classes for thedata structures we need. One step remains: we have to tell theBankingService to use our concrete classes. Where should thishappen?

It should not happen within BankingService itself. Thewhole point of encapsulation is that BankingService shouldn’tknow which specific data structures it is using. The only other wayto get a specific object into a BankingService object isthrough the constructor. This is the answer: when you create aBankingService object, pass it objects of the specific datastructure that you want to use.

class Examples {

BankingService B = new BankingService(new AcctSetList(),

new CustSetList());

...

}

This illustrates how we create different banking services withdifferent data structures for accounts and customers. If we had anAVL-tree based implementation of IAccountSet as a class namedAcctSetAVL, we could create a different banking service using:

BankingService B = new BankingService(new AcctSetAVL(),

new CustSetList());

Since BankingService only uses methods in theIAccountSet and ICustSet interfaces, we can freely chosea data structure without editing the code within theBankingService class (which was our goal).

3.4Anticipated Questions

  • This banking service has no customers or accounts. How dowe populate those?

    In a full banking service program, the IAccountSet andICustSet interfaces would also need methods for adding newelements.These notes do not include these in order to stay focused on the topicat hand.

  • If I have to specify the data structure to use in theconstructor, how would I switch to a new data structure after mybanking service had been running for a while?

    These notes have not addressed this question. To do this, you wouldfirst need a method to convert your existing data from onerepresentation to the other. Then, you would either create a newBankingService with the new data, or use some method providedin the BankingService to update the data structure.

    Something to think about: Providing a simple setter method would letyou change the data structure. What’s wrong with this solution? Whatwould a better solution look like?

4Summary

Compare the original banking code to the revised version. The newBankingService is much cleaner and more maintainable. Itallows the information about accounts and customters to change withless impact on the banking service methods. The banking service nolonger relies on any particular data structure for accounts andcustomers. We achieved both of these goals by isolating data andmethods in classes, and using interfaces to separate general data fromimplementation details.

Key take-aways from these lectures:

  • Encapsulation is about putting data and the methods that operateon that data together. OO classes provide a natural mechanism fordoing this.

  • Encapsulation matters because it lets you change data and how itis used without editing existing code. This lecture has shown twoexamples of this:

    • We might add information (such as a withdrawal fee) and want tochange methods (such as withdraw) to use that information.

    • Writing code that can be customized to different specific datastructures (such as linked lists versus AVL trees).

  • Java provides access modifiers that let you control whichother classes can access your methods and fields. You should putexplicit access modifiers on all of your fields and methods.

  • Encapsulation is NOT just about protecting your data. It isabout your class hierarchy (more generally, the architecture ofyour program). The Java access modifiers are used in conjunction withencapsulation (and indeed help reinforce encapsulation), butencapsulation is a much broader topic.

Encapsulation is an important issue no matter what language you areprogramming in. Different languages provide different support forencapsulation. Java’s support comes in the form of classes andinterfaces (supported by access modifiers). Other languages haveother mechanisms. When designing a new product in any language, it isimportant to ask what information you want to protect and whatdecisions you want to be able to change later, then understand how thelanguage can help you achieve those goals.

4.1Two Myths About Encapsulation

Those of you with prior Java experience may have heard two generalguidelines or slogans that aren’t quite accurate:

  • MYTH: Always make a getter and setter for every private field.No. Sometimes, we have fields that we don’t want anyone to haveaccess to – they are for internal use only. Creating getters/settersfor such fields contradicts the design goal of those fields. Wouldyou publish a getter for the password field of Customer, forexample? No – that data should only be handled within the Customer

    This rule can also violate representation encapsulation. Imagine thatICustSet provided a method getCustomers that returnedthe customers field. That would defeat the whole purpose ofhiding the representation, because a programmer could get the actualdata structure and write code against it (like a for-loop against theLinkedList). This is a great example of where the getter isexactly the wrong thing to provide.

  • MYTH: Encapsulation equals Information Hiding. These conceptsare related, but not equal. One can encapsulate data and methods, butstill not hide information (if you expose everything through getters,for example). Encapsulation is part of a solution for hidinginformation, but information hiding needs careful design beyond justputting all data and methods together in a class.

Encapsulation and Information Hiding (2024)
Top Articles
Techniques de pirates - Comment les cybercriminels blanchissent l'argent du carding (Cash Out) ? | UnderNews
42 Easy Ways to Make Money Fast (Earn $100+ Today) - DollarSprout
English Bulldog Puppies For Sale Under 1000 In Florida
Katie Pavlich Bikini Photos
Gamevault Agent
Pieology Nutrition Calculator Mobile
Hocus Pocus Showtimes Near Harkins Theatres Yuma Palms 14
Hendersonville (Tennessee) – Travel guide at Wikivoyage
Compare the Samsung Galaxy S24 - 256GB - Cobalt Violet vs Apple iPhone 16 Pro - 128GB - Desert Titanium | AT&T
Vardis Olive Garden (Georgioupolis, Kreta) ✈️ inkl. Flug buchen
Craigslist Dog Kennels For Sale
Things To Do In Atlanta Tomorrow Night
Non Sequitur
Crossword Nexus Solver
How To Cut Eelgrass Grounded
Pac Man Deviantart
Alexander Funeral Home Gallatin Obituaries
Shasta County Most Wanted 2022
Energy Healing Conference Utah
Aaa Saugus Ma Appointment
Geometry Review Quiz 5 Answer Key
Hobby Stores Near Me Now
Icivics The Electoral Process Answer Key
Allybearloves
Bible Gateway passage: Revelation 3 - New Living Translation
Yisd Home Access Center
Home
Shadbase Get Out Of Jail
Gina Wilson Angle Addition Postulate
Celina Powell Lil Meech Video: A Controversial Encounter Shakes Social Media - Video Reddit Trend
Walmart Pharmacy Near Me Open
Marquette Gas Prices
A Christmas Horse - Alison Senxation
Ou Football Brainiacs
Access a Shared Resource | Computing for Arts + Sciences
Vera Bradley Factory Outlet Sunbury Products
Pixel Combat Unblocked
Cvs Sport Physicals
Mercedes W204 Belt Diagram
Mia Malkova Bio, Net Worth, Age & More - Magzica
'Conan Exiles' 3.0 Guide: How To Unlock Spells And Sorcery
Teenbeautyfitness
Where Can I Cash A Huntington National Bank Check
Topos De Bolos Engraçados
Sand Castle Parents Guide
Gregory (Five Nights at Freddy's)
Grand Valley State University Library Hours
Holzer Athena Portal
Hello – Cornerstone Chapel
Stoughton Commuter Rail Schedule
Selly Medaline
Latest Posts
Article information

Author: Pres. Carey Rath

Last Updated:

Views: 5913

Rating: 4 / 5 (61 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Pres. Carey Rath

Birthday: 1997-03-06

Address: 14955 Ledner Trail, East Rodrickfort, NE 85127-8369

Phone: +18682428114917

Job: National Technology Representative

Hobby: Sand art, Drama, Web surfing, Cycling, Brazilian jiu-jitsu, Leather crafting, Creative writing

Introduction: My name is Pres. Carey Rath, I am a faithful, funny, vast, joyous, lively, brave, glamorous person who loves writing and wants to share my knowledge and understanding with you.