2
$\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 possbile give them some "real" example to try themselves. But most of materials I find (most of google 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 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 another 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 unlike to appreciate that.

---- update ----

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

When using 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 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 of python logging module, I hope someone who is familiar with 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 its 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 ...

$\endgroup$
3
  • 1
    $\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 '20 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$ – Aksakal almost surely binary Dec 31 '20 at 19:30
  • $\begingroup$ School, Student, Teacher, Course, Assignment $\endgroup$ – Luke Sawczak Mar 2 at 11:49
3
$\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 name, 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 of 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 patters 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$ – Qiulang Dec 31 '20 at 15:34
  • $\begingroup$ I am quite familiar with todomvc.com I probably can adapt it to my teaching. $\endgroup$ – Qiulang Jan 1 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 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$ – Qiulang Jan 1 at 15:27
  • $\begingroup$ I found an interesting answer Alan Kay gave and updated my question again. $\endgroup$ – Qiulang Jan 13 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 which 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$
-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$
3
  • 4
    $\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 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$ – Qiulang Jan 2 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 at 22:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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