4
$\begingroup$

I have taught my high school students to write some simple python scripts, taking some ideas from "Automate the Boring Stuff with Python", e.g. changing file names in a folder to a specific naming pattern.

My next goal is to teach them object-oriented concepts and if possible give them some "real" examples to try themselves. But most of the materials I find (most of google's top search results) about OO programming (not just python) are laden with poor examples, if I may say so. I really don't like to use car/vehicle or animal/dog to teach them the OO concept and OOP.

A. it is boring (you know high-school kids)
B. it has no real use.
C. as one comment said "they are fatally flawed." (But if you really like to use the animal example, check this "Object-Oriented Design")

I plan to use pathlib (btw, they already have a basic idea of the difference between Windows and UNIX)

pathlib

GUI development could be another good example to teach OOP but I don't want to teach them GUI yet.

Any suggestion to introduce OO with the "real" example?

BTW, I’m not a strong advocate of object-oriented programming. But the pathlib.Path class is a useful abstraction especially compared with os.path, although the high school students are unlikely to appreciate that.

---- update ----

I find the python logging module is another good example, but it seems too complicated for non-experienced programmers.

When using the logging module we mainly use 3 objects, Logger, Handler, Formatter. Logger is the facade, as https://docs.python.org/3/howto/logging.html#loggers said,

Logger objects have a threefold job. First, they expose several methods to application code so that applications can log messages at runtime. Second, logger objects determine which log messages to act upon based upon severity (the default filtering facility) or filter objects. Third, logger objects pass along relevant log messages to all interested log handlers.

Handler is a good example to use inheritance https://docs.python.org/3/howto/logging.html#useful-handlers and handlers use formatters, which hide the complexity of LogRecord, to the format log message.

In addition to these classes, there are a number of module-level functions for convenient use.

Overall it is a good example of using composition and aggregation.

But I am not an expert on the python logging module, I hope someone who is familiar with the python logging module may add an answer to my question.

--- update 2 ----

I found Alan Kay answered the question “What are the five features of the object-oriented paradigm that you consider to be important for good software engineering practice?” with these words, in my opinion, resonate with the answer Buffy gave. But how to convey these ideas to high school students or non-experienced programmers is another challenge,

a “part” construct that can protect it's inside from its outside, and vice versa

a “communications” construct that can convey interactions and deal with dependencies

a “system” construct that is a combination of parts and communications that can fit in a part recursively, and everything is made this way

the “messages” that are communicated are also in terms of the systems

the system that is being made is made with the same kinds of system ...

--- update 3 ---

The recent log4j security flaw made me come back to my own although my question has nothing to do with log4j. I know python logging module is adopted from Log4j, so I think this can be used as a good example to how different programming languages borrow the idea from each other. If the students have some knowledge about java, I may consider to introduce them this old article "Python isn't just Java without the compile"

BTW, Buffy gave answer about calculator as an example to practice OO, but I will argue it will depends on how you design the question and the project, e.g. here "How To Make a Calculator Program in Python 3" without introducing OO concepts at all.

$\endgroup$
6
  • 3
    $\begingroup$ car/vehicle, animal/dog are not only poor, they are fatally flawed. In teaching OO, you are not, fundamentally, teaching inheritance. I'll try to come up with something to suggest. $\endgroup$
    – Buffy
    Dec 31, 2020 at 11:51
  • 1
    $\begingroup$ Real practical example should be just that : real code from libraries or apps used in practice. For python look at popular libraries code such as numpy. Don’t do animals. Pull a hierarchy such as dataframe/series from pandas for instance. $\endgroup$ Dec 31, 2020 at 19:30
  • $\begingroup$ School, Student, Teacher, Course, Assignment $\endgroup$ Mar 2, 2021 at 11:49
  • $\begingroup$ If you do show them the article Python, you should mention that it's quite dated (in the sense that modern Java does almost every one of those things), but that the article is still worthwhile because of its super clear explanations of a number of programming concepts, and to illustrate that different languages really do have meaningful differences. (Again, this list of specific differences is now dated, but that if we polled the languages today, there would be other differences now.) $\endgroup$
    – Ben I.
    Jan 7, 2022 at 12:18
  • $\begingroup$ I know! The article was published in 2006 and I can't find a modern version of that. $\endgroup$ Jan 7, 2022 at 12:23

4 Answers 4

5
$\begingroup$

I expect to update this a few times and hope to eventually have a long answer.

But let me start by explaining some things that you may already be aware of, but other readers may not.

First, OO programming is not, fundamentally, about inheritance and too many books and authors fail to understand that. Moreover, they use inheritance in a dreadful way that makes software hard to understand and harder to maintain.

For example, note that the Linnaeus style hierarchy of the biological world is almost entirely one of "interfaces" and not even "abstract classes", much less concrete classes. There are no instantiated "mammals", for example. It is only at the leave of the hierarchy that things actually exist, other than as ideas. There is some genetic continuity, of course.

Second, there are some principles that can guide you in writing and teaching OO design, but they require discipline. And part of that discipline is to control the situations in which you "break" the rules.

My first advice for anyone wanting to be an OO programmer is not to think in terms of inheritance, but, rather, of composition. Complex things (objects) are composed of other things (objects) that are a bit simpler than the containing object and provide it some essential service. If you write classes where all (or even most) of your instance variables are language primitives, then you don't really get it. And if those instance variables (objects or primitive) have a lot of getters and setters, then you aren't doing OO Programming at all.

In fact, making the above mistakes, requires that the programmer keep track of all details at all points of the program where OO is designed to capture decisions so that they needn't be "checked" again. Set it and forget it, so to speak.

So, if you want to build an automobile, don't think of it as a subclass of vehicle (which gains you nothing: a personal drone and a battleship are both vehicles). Think of it instead of being composed of various parts: engine, transmission, controls, accommodations, etc. And those parts are, themselves, composed of parts. An engine has igniters and pistons, exhaust, etc. Many of those are composed of parts also. It is only at the lowest, simplest level that you build with primitives.

Two of the principles that are worth keeping in mind and following almost always are the Liskov Substitution Principle and the Law of Demeter.

The first suggests that if you extend a class with a subclass you don't also extend the public interface of the subclass. Then all subclass objects are substitutable, differing in behavior but not in interface. Demeter, on the other hand, forces you to write more explicit code that makes relationships clear to a reader. Of course it also forces you to introduce more names, and if they are intention revealing names then your code is clearer.

The Liskov principle, of course is one of the elements of SOLID which you also need to incorporate into your thinking.

In my own programming I'm very faithful to Liskov, also defining Interfaces for most things before I write classes. I'm less faithful to Demeter in the heat of battle, cascading messages. But then I sometimes also need to unravel those a.b.c.d sorts of cascades to figure out what I really mean.

One goal I have in writing OO code is to seek to write only very short methods with a minimum of structure. In other words I try to minimize Cyclomatic Complexity. My palms start to get itchy after the fourth statement in a method or if the complexity level reaches three. I can't always get away with that, but it is a goal. The solution is to refactor complexity mercilessly. Factor out methods, of course, but also "parts", creating new classes to manage the complexity. Even if many of those classes are Singletons the code is usually improved and having the goal in mind from the start, means that the refactoring step is less needed.

Design patterns are tools that you need to be an effective programmer in most OO languages. Especially helpful ones are Strategy, Decorator, Observer, and Iterator. Most of these are actually used to build the various Java libraries.


Now, to get to the actual question asked here. But note that a lot of learning can take place even if the students never reach completion of the project in the allotted time. An agile approach (Extreme Programming, say, with you as "Customer") to development leaves them with some functionality even if not all specifications are implemented.

Dungeon Game

Build a text based dungeon game. The main objects are characters (people), places, and things. The places are organized in some sort of map, maze, or grid. Things happen when characters enter places. Characters find and carry things. The things have actions, depending on the kind of thing. A "spell" is a "thing" whose action might depend on context. A "transporter" object might work differently in different rooms (strategy objects).

Note that the classic board game Chutes (or Snakes) and Ladders is a simplified version of this. A text based version avoids a lot of complexity.

Calculator

A calculator has parts such as keys and the display. Less visible are the internal memory, possibly a stack. Even the operations can be objects. The behavior of the keys changes, depending on the state of the computation (Strategy pattern). It is possible to build a simple calculator without a single IF statement, actually.

Abstract Computer with an Assembly language

A Stack Based computer processor simulation is pretty straightforward. There may be accumulators and such, but a single stack on which all operations are done is simple and complete. The operations can be objects (parts). One advantage of this example is that most of the necessary methods can be very short. A program can be read with a Java Scanner object. It needs a program counter at least and possibly a frame pointer if the language is to support subroutines.

Sticky Notes (I haven't tried this)

An application that lets users keep notes and cross reference and organize them. Some classes might be Notes, Keywords, Connections, Lists.

Jeopardy

A simulator for the jeopardy game from TV. Categories, Answers, Questions, Teams, Scores.

(Back soon, maybe).

$\endgroup$
5
  • $\begingroup$ I have been struggling to a find a way teach OO because I have my doubt. Check my question here softwareengineering.stackexchange.com/questions/419122/… $\endgroup$ Dec 31, 2020 at 15:34
  • $\begingroup$ I am quite familiar with todomvc.com I probably can adapt it to my teaching. $\endgroup$ Jan 1, 2021 at 2:38
  • $\begingroup$ There are links to and explanations and examples of MVC (in Java) here: csis.pace.edu/~bergin. Sadly, the applet version no longer runs on the web. $\endgroup$
    – Buffy
    Jan 1, 2021 at 13:18
  • $\begingroup$ todomvc is not really about mvc but the the choices and the comparison of different web technologies to implement a todo list. I mentioned because you mentioned Sticky Notes. $\endgroup$ Jan 1, 2021 at 15:27
  • $\begingroup$ I found an interesting answer Alan Kay gave and updated my question again. $\endgroup$ Jan 13, 2021 at 4:55
1
$\begingroup$

I found a Python class to be so simple to write (for "mundane" tasks), that they can grow naturally out of a desire to simplify and refactor code in development. It is very much a pragmatic bottom-up approach; you were expecting to hack some simple code for a particular purpose, then it grows a bit and you find you have several functions handling "structures" that were tuples or arrays or even globals. Suddenly, you see the light and create a class, and the code size gets divided by 2 or more, and is so much simpler.

This is an easy way to argue for OOP, by taking some "flat" concrete existing code and looking for the abstraction that could be made, moving the data out of the function parameters, into the class, and using self.

Things like inheritance can similarly be discovered almost naturally; you use an existing class that doesn't quite do what you want and you need to change it; instead of copying and modifying it, you subclass and change or add a method.

For concrete examples, you can look at computer hardware itself. At a low level, registers are often partitioned into several disparate functions. If you want to set a feature bit to 1, you need to shift it left 20 bits, read the current value of the register, mask out bits 0 to 5 as they are "write 1 to clear", and so on. Try emulating a serial port 16550 uart; it is good for the soul. And, of course, if you use MicroPython running on a real microprocessor, you could probably even try out your code.

$\endgroup$
-1
$\begingroup$

My go-to example for both data modeling and OOP (which in both cases revolve around a kind of normalization), is a video rental store. It's maybe a very dated example, feel free to change it to a library or a rental store for other things, but I find that the video store example both highlights the intricacies of OOP and data normalization, while at the same time being a very simple context to grasp.

The main goal is to build up to a three table/class diagram: Customer, Video and Rental (which is the cross table between customers and videos).

The rest of this answer is just tips on how to relate specific OOP basics to the example at hand.


Why objects?

Well, how would you store your video data, if you want to track three things for a rental: the customer name, address, video name, and expected date of return.

Ask the students to write a very simple program that is able to tell you that Alice rented Antz, Bob rented Bee Movie, and Charlie rented Cars. Let them rely on some reusable PrintRentalInfo method, but allow them to define the method parameters as they see fit.

Students who haven't yet seen OOP will use four distinct arrays and rely on the fact that one video is found on the same index in all four arrays. Explain to them that instead of having a "bag" of customer names, a "bag" of addresses, a "bag" of video names, and a "bag" of return dates is not really easy to work with. Suggest the idea that instead of making a "bag" per data field, it would make more sense if we made a "bag" per rental.

Build the Rental class with the four properties. Build the same application as they did, but using OOP. This will show them object initialization, how different objects have the same structure but individually unique content, and how you can pass an object around (as opposed to multiple method parameters of primitive types).

Really highlight how easy it is that you can move this rental "bag" around from method to method, keeping all relevant information together.


Why more than one class?

A fourth customer appears. Her name is also Alice. You run into an issue here, because now you're unable to tell which Alice has rented which videos, and you don't want to fine the wrong Alice.

Also, the original Alice has called us to let us know her address has changed. Point out the difficulty of having to go through all the rentals and figuring out that you can't just blindly change "old_address" to "new_address", because there may be other customers living on that same address who did not move with Alice. Also, you can't rely on the name either, as the fourth Alice has already made some rentals too.

If the students keep protesting that you could do it based on the combination of name and address, as what happens if those two Alices live on the same address, and only one of them moves.

Suggest to the students that it would make a lot of sense if we had a separate list of all our customers and their addresses, so we could tell them apart by more than just their name, and easily change one person's details.

Goal: build a Customer class, and change Rental so it includes a Customer property as opposed to name/address properties.

Focus: very much highlight how you can have two different customer objects, even when their names and addresses are the same.

Extra: You can take the same approach to creating the Video class, so you can track the specific videos you have. Maybe leave this as an exercise to the students, as this is much of the same as before.


From this point on, you can expand the business logic based on what you want to showcase.

  • Inheritance - Maybe the store rents Video and Game, but you still want Rental to be able to link to either of them (using the base RentableObject class)
  • Interface - You can use the same example as inheritance.
  • Data normalization - How would we go about both tracking the movies we have available, and the individual physical cassettes (we could have multiple of the same movie)? How would be be able to track fines that a customer has incurred, and which of them they have already paid?
  • Data transformation - Our boss wants us to print a weekly report on all rentals made, rentals returned, incurred fines, and fines from before which still haven't been paid.
  • Reference vs value - Add a Price to both the Video and Rental. Showcase how you set rental.Price based on video.Price, but when video.Price later changes, rental.Price is untouched. Now repeat the same exercise with a reference object (e.g. changing the customer's name).

I find that the context for this example is very easy to grasp, and has a lot of opportunity for expansion. This could grow into a long-term project that you keep expanding, which can be a valuable lesson if you want to teach your students about how to handle changing requirements and the benefits of clean coding, or maintenance and legacy development.

$\endgroup$
8
  • $\begingroup$ Thanks for the ideas! Some suggestions, a. I use python so I believe we will first use dictionary not array to build the first version b. this is a good example to introduce 'id' concept, id for customer, id for video. $\endgroup$ Jun 2, 2021 at 10:17
  • $\begingroup$ @Qiulang: Absolutely. While this does muddy the line a bit between OOP and data normalization, these two concepts are thematically linked and often feed into each other in actual business projects, so I very much like that you learn a bit of both at the same time. $\endgroup$
    – Flater
    Jun 2, 2021 at 10:20
  • $\begingroup$ A video store is a nice example of an object-oriented design. But how do you use it? Are you going to keep an array of customers, and at random times they visit the store? What are those times? How do you make this realistic? That driver program may actually be harder to program than the classes. And how do you determine that your whole program does what it's supposed to do? $\endgroup$ Jan 6, 2022 at 12:02
  • $\begingroup$ @VictorEijkhout: I'm not quite sure what you're focusing on here. The answer is focused on an example that drives the design of a video rental application, not a simulation of how it will be used during a real day. How to realistically simulate usage in a specific scenario is a completely different topic and not at all something OOP-learners should be concerned by. Part of this exercise is to let the students find a solution and then showcasing betters ways of doing the same thing; which precludes giving a pre-formatted input as this would start revealing the intended design. $\endgroup$
    – Flater
    Jan 6, 2022 at 12:31
  • $\begingroup$ @VictorEijkhout: Your last question implies you're bring up TDD (or some kind of testing at least). While I appreciate the value of TDD (and testing in general), the first introduction to OOP is not the right time to also introduce testing, nor should the two be merged into a single practical exercise for the students. $\endgroup$
    – Flater
    Jan 6, 2022 at 12:36
-2
$\begingroup$

How about a boring*, but realistic, commercial example? When the students leave the "hallowed halls" and become entrepreneurs in start-ups, they will be faced with the relationships between Customer, Product, and Sale (Transaction).

By keeping all of the Product data (for example) 'together', we introduce the advantage of encapsulation over a basic data structure, eg array. There is scope for hierarchies (later).

Per earlier "composition" responses, when we look at an Address we see that each Customer must have one, oh wait, more than one if his/her billing address is distinct from the delivery address. So, if an Address is part of a Customer, should Customer "inherit" from Address? ("is a" cf "has a"!) What about the difference between a personal customer and a corporate?

Do both have a name, and address, ...? How are they similar/where do they differ? How do we keep track of the individual from the Corporation with whom we are dealing?

What happens when we make a sale? Is this data to be part of the purchasing-Customer's object, or the Product object - or objects, or what? Thus, "Interfaces" (in some languages), an invitation to expand the SRP into micro-services, drop into APIs...

There's plenty of scope for SOLID principles, best practices, language differences, and all-the-fun-of-the-fair. Meantime, you could 'dress up' problems to make the toy-examples seem 'real' any-which-way; and the students are learning 'how the world works' and thus (hopefully) enthused to progress.

  • personal opinion: can't imagine how Pathlib avoids the "boring" criterion (besides it has its own struggles with 'the concrete', given its abstraction from the OpSys - which is already an abstraction ... Alice, rabbit hole, Toto != Kansas...)
$\endgroup$
4
  • 5
    $\begingroup$ Actually, no, a customer is not a subclass of address. That makes no sense. If it makes no sense then the software is conceptually broken and can't be maintained. One of the worst ideas ever to appear in an elementary book was to make Cylinder a subclass of Circle, just because the Circle class already had some fields that a Cylinder also needs. $\endgroup$
    – Buffy
    Jan 1, 2021 at 21:46
  • $\begingroup$ path is something they know and they notice the difference between windows and unix. Teaching pathlib really teaching something they can learn, think, explore. So it is not boring (my opinion). $\endgroup$ Jan 2, 2021 at 6:07
  • $\begingroup$ Apologies @Buffy, it may not have been apparent that I use a Socratic approach towards trainees' learning. You and I both know about sub-classing and composition! $\endgroup$
    – d-n
    Feb 4, 2021 at 22:21
  • $\begingroup$ @Buffy: The inheritance idea was phrased as a question for the teacher to explore with the students. Not as the right answer. Exploring bad ideas that most (if not all) developers intuitively think of before they understand why it's a bad idea, is a really good and pro-active way to help your students get ahead of the curve as opposed to knowingly and unguidedly letting them fall into the trap. $\endgroup$
    – Flater
    Jun 2, 2021 at 9:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.