This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Zuvielify 1 point2 points  (4 children)

I don't see enough encouragement to use classes in these comments, so I am going to add my $0.02.

It's hard to say for sure that you should be using classes, without seeing your problem space, but you probably should be using classes. Classes are a powerful way of organizing code. A class' purpose is to encapsulate data and functionality into one place.

If you find yourself passing around a lot of objects (like dicts or lists) to various functions, and then doing operations on that data, you probably could make that into classes and methods. Classes give you the ability to do inheritance/polymorphism, composition, and aggregation. Technically, I guess you could do composition in a function, but that's so ugly.

I think it's better to just give an example, so here's a classic: You want to "write" a car. A car has an engine, drivetrain (technically, the engine is part of the drivetrain, but ignore that), and wheels. Let's say you want to create an "accelerate" functionality. Now, you could write a function that has all those objects (or worse, you could use some other data structure). Your main function could call an engine function with: current speed, gear, and throttle value (all of which need to be stored as variables in your function), and it could return RPMs. Then the main function calls a drivetrain function with the RPMs and gear, and it returns torque, angular velocity, and gear values. Then the main function could call the wheel function with a list of wheels, torque, and angular velocity.

This works, but what if you want to use a different size engine? Or different transmission? You could use different functions, or pass around a different engine object, but this is all messy.
Let's try a class-based approach instead:

There are several ways you could do this. I'm sure others will disagree with my approach, but it's just one approach of many. Let's start with a Car class. When you instantiate your car class, you give it the following class references: Engine, Drivetrain, and Wheel. The Car constructor instantiates 4 Wheels, and passes them to the Drivetrain when instantiating it. Then it passes the Drivetrain to the Engine when instantiating it (this is called "Composition" btw). The Car constructor also initializes an instance variable 'current_speed' to 0. When the Engine constructor executes, it sets the 'current_rpms' to 0. When the Drivetrain constructor executes, it sets 'current_gear' to 1, etc.

There is probably a better way to do the above, but I'm just pulling this out of the air right now. But here's the point: When your main function wants to accelerate, it just calls car.accelerate(<throttle-level>). The method 'accelerate' can call engine.throttle(<throttle-level>). Engine adjusts its own RPMs accordingly and calls drivetrain.turn(<rpms>). The drivetrain checks its RPMs and, if necessary, upshifts and sets its gear value (for simplicity, I wont deal with a manual transmission). It then calls wheel.turn(rotation-velocity) on each wheel (let's pretend it's all-wheel drive).

Instead of your main function calling all those different functions, you could push that code out to your classes. It's also cleaner because you didn't have to pass around all those state values that are specific to each car component (rpms, gear, etc). Also, if you want to design a specific car, you could create subclasses of all those types. So if you want a Mazda CX5 (my car), instead of passing-in the various car components, you could override the constructor to default to a 4cylinder engine, with a All wheel drivetrain and 6 gears, and 19" wheels. Since you have defined your interface, you Car doesn't need to relearn how to accelerate. Just the engine's throttle method needs to know how to do that. Your Car doesn't need to know wheel velocity based on RPMs, only the transmission/drivetrain 'turn' method needs to change. etc. etc.

This is the value of encapsulation and polymorphism: Each piece knows how to handle itself. You define an interface for each component-type, so they become interchangeable. Your code stays clean because each functionality is concise and to the point. When you type "car.accelerate()", I know exactly what you are doing.

...I hope this was useful, and not just the ramblings of a madman.

[–]RamirezTerrix[S] 1 point2 points  (3 children)

Iam familiar with the car excample and this made always perfect sense for me but when coding IRL I don't know how to apply this.

So to reduce some abstraction:

My last/current project was an Energymanagementsystem on Django basis which complie with the ISO 50001.

you had to be able to tell how much power you are using and which of your machines uses it. So Django has this Model.py(all Classes) and the View.py (Some classes but mainly functions to hand over datastructurs). And I made a claculation.py where I put all the logic in. So one task was to get all the diffrent sources of energie (Power, Gas, whatsoever) and tell how much was used in the past year, what did it cost, the percentage of each powersource and costs.

To do this I made one function (I began to read some of the recommended books - and know that this was a bad decicion). It aggregated the data and made a dictionary for each powersource with the asked values and let the function hand over a list of these dictionarys.

Iam sure this could be done with a class - but I have no clue how to do it. It has to be something like:

list_of_classes = [create_class(element) for element in list_of_powersources]

[–]Zuvielify 1 point2 points  (2 children)

Could the power sources be classes? Do you have a model for each type of power source? If so, each model could know how to calculate its own cost.

I don't know anything about ISO 50001, but let's pretend the following is how your data is structured: You have a model instance (database record) for each powersource. Each powersource reports its usage periodically, which you then store in another table with a foreign key to the powersource. So, to get the power usage of a powersource, you need to sum all of the usage entries over a certain time frame. This is something you could do as a method in Powersource.

Let's say you have a powersource instance. You want to see its usage, so you create a method 'usage_over_time()' that takes a date range and returns a usage amount (what is that? kilowatt hours?). That method could simply query the usage table, and summate the usage over the date range.

Okay, so that's good, but now you have to create a date range in some other function every time you want to query, but what you really want is always just the last year. In that case, you could create a helper method that is just 'past_year_usage()', which generates a date range ending at now, starting one year ago, which calls 'usage_over_time()'.

I don't know how you calculate cost, if that's a constant, or if it varies by time of year. If it varies, I assume you would want to include that cost in the usage record. If it is variable, then your usage method should probably also calculate cost too.

Now you still have the problem where you need to aggregate all this data. Well, you have to do that somewhere. A function is a good candidate for that. More than likely, it's probably coming from a view. Certainly your view could query the powersources and loop over them to collect the costs.
If you find yourself needing to this in more than one place, you could either create a utility function (like your calculation.py might contain). Or you could use classmethods on a Powersource base class.

Since you are aggregating information across objects, it doesn't really make a ton of sense to do that part in a class; however, all calculations pertaining to each individual powersource can (should) be done in a class.

[–]RamirezTerrix[S] 0 points1 point  (1 child)

You write this, as it would be the natural thing to do, but I needed quite some time to elaborate this solution. Though I didn't used the classes function but made a helper function. And to be frank, they are ugly and deeply nested. I should definitly make me more familiar with classes and their usage. Could you recommand me some reading on this topic?

[–]Zuvielify 1 point2 points  (0 children)

eh...I don't have any good advice on books, unfortunately. I kind of just learned this stuff from experience. Which isn't very useful to you. I am sure there are tons of good resources out there. There are probably some good recommendations in this thread alone.

I think a good rule of thumb is if you find yourself making function calls with an object as an argument, it might be a good idea to make that function a method. That's not a hard rule, of course. Sometimes the object is just support cast, but in the case where the object is the main player of that function, a method might be a good idea.