Some real practical example to teach object-oriented concepts and programming (in python) I have taught my high school students to write some simple python scripts, taking some ideas from &quot;Automate the Boring Stuff with Python&quot;, 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 &quot;real&quot; 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 &quot;they are fatally flawed.&quot;  (But if you really like to use the animal example, check this &quot;Object-Oriented Design&quot;)
I plan to use pathlib (btw, they already have a basic idea of the difference between Windows and UNIX)

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 &quot;real&quot; 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
...
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 &quot;interfaces&quot; and not even &quot;abstract classes&quot;, much less concrete classes. There are no instantiated &quot;mammals&quot;, 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 &quot;break&quot; 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 &quot;checked&quot; 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 &quot;parts&quot;, 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 &quot;Customer&quot;) 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 &quot;spell&quot; is a &quot;thing&quot; whose action might depend on context. A &quot;transporter&quot; 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).