Multiple classes object "chaining"? - python

I am new to Python and I have prior experience in VBA.
I wish to create Classes in Python such that I could achieve the following syntax:
Company('MS').Department('Finance').Employee('John').FullName = 'John MacDonalds'
Company('MS').Department('Finance').Employee('John').Wages = '5000'
I am beginning to realize that declaring the Class structure in Python is very different from VBA.
Am I on the right approach? If not, will there be any other recommended approach for this Parent/Child structure?

I really don't think that's a good use of inheritance. Good inheritance is when the child class can easily be substituted for the parent class with no difficulties. Is an employee substitutable for a department? That doesn't seem right. Wouldn't a department be able to do tons of things an employee couldn't? For every function you have for a department that an employee can't do, you would have to have a "throw new not implemented" in the child class.
This is known as the Liskov Substitution Principle. Basically subclasses must be substitutable for their base classes. My favorite example is with birds and penguins. You could make a bird class and have lots of different kinds of birds inherit from this class. But what happens when you create a penguin class inheriting from bird? Penguins can't fly but birds can. So in the penguin class, you would implement this function and would have to say "throw new not implemented." Its not good because if you put a penguin in where a function is expecting a bird, it will freak out and crash when you give it a penguin.
I hope that helps. I'm not too familiar with Python specifically but I do understand inheritance.

You haven't really said what the syntax you want is supposed to accomplish which would be useful to know...
Nesting classes as shown below would allow you to achieve the syntax you want, but the result is not only very "unpythonic" — it's also completely useless and I can't think a reasonable way to implement to change that.
Without more information about what you're doing, at the moment I can think of an alternative to suggest. I suggest you just create a bunch of regular non-nested classes and explicitly instantiate them when needed.
Note that I've changed the names you were to follow the PEP 8 naming conventions.
class Company:
def __init__(self, name):
self.name = name
class Department:
def __init__(self, dept_name):
self.dept_name = dept_name
class Employee:
def __init__(self, given_name):
self.given_name = given_name
Company('MS').Department('Finance').Employee('John').full_name = 'John MacDonalds'
Here's a way to implement your tree structure via Python "autovivification" using the Vividict dictionary defined in this answer.
I've adapted it to support different Vividict subclasses at each level in the hierarchy: i.e. Company, Department, and Employee. Each subclass defines a class atribute named subtype the the Vividict base class' __missing__() will use when creating missing key values. I also added __repr__() method to each one to make instance print out all the items they contain.
This is just a proof-of-concept and I really don't know if you can use it or not, but it should give you a good idea of one way of doing what you want with some fairly readable syntax.
class Vividict(dict): # Base class.
subtype = dict
def __missing__(self, key):
value = self[key] = self.subtype(key)
return value
class Employee: # Leaf - Regular class
def __init__(self, given_name):
self.given_name = given_name
def __repr__(self):
return f'{self.__class__.__name__}({self.given_name!r})'
class Department(Vividict): # Branch.
subtype = Employee
def __init__(self, dept_name):
self.dept_name = dept_name
def __repr__(self):
return (f'{self.__class__.__name__}({self.dept_name!r})' + '\n '
+ repr(list(self.values())))
class Company(Vividict): # Root of tree.
subtype = Department
def __init__(self, co_name):
self.co_name = co_name
def __repr__(self):
return (f'{self.__class__.__name__}({self.co_name!r})' + '\n '
+ repr(list(self.values())))
company = Company('MS')
company['Finance']['George'].full_name = 'George Brown'
company['Finance']['Mary'].full_name = 'Mary Jones'
print(company)
Output:
Company('MS')
[Department('Finance')
[Employee('George'), Employee('Mary')]]

Related

Edit parent object's attributes in a nested object

I am trying to solve a problem where I have a class which is used to store objects of other classes. I want to be able to create a list in that parent object of certain types of nested objects. (sorry if I'm phrasing this badly)
For my example I am taking a real world problem of a House. In the house we may have many different 'things'. We could have people, pets, furniture, computers etc.
I want to be able to store this in a nested format so I create a House with any of those sub-objects within.
E.g
old_rectory = House(
Person('Barry'),
Person('Bob'),
Pet('Bill'),
Pet('Brenda')
)
I have created 3 classes: the first is for the House itself, then I have a class for people and a class for pets.
For the house class I use *argv so that I can add as many other objects as necessary.
Now in the example above I would like to be able to access old_rectory.people and see a list of any instances in old_rectory that are of the type Person.
class House:
def __init__(self,*argv):
self.people = []
self.pets = []
for arg in argv:
if isinstance(arg, Person):
self.people.append(arg)
elif isinstance(arg, Pet):
self.pets.append(arg)
class Person:
def __init__(self,name):
self.name = name
class Pet:
def __init__(self,name):
self.name = name
You can see that I have achieved this by hard coding the lists and some if conditions into the House class. But this means that whenever I create a new class I also need to add in a new list and the logic to the House class.
I would like to be able to access the house object's attributes from the individual classes (Person, Pet, etc.) but I am unsure of how to proceed or if it is even possible.
class Doors:
NOW CREATE THE NEW LIST IN HOUSE CLASS
def __init__(self,name):
self.name = name
NOW APPEND THIS OBJECT TO THE NEW LIST IN THE HOUSE CLASS
I can see two clear patterns on how to try to overcome this:
Create methods for getting the instances of a specific class
Simply put every instance inside a big list and add methods to get each "sublist":
class House:
def __init__(self,*argv):
self.things = [*argv]
#property
def pets(self):
return [thing for thing in self.things if isinstance(thing, Pet)]
#property
def people(self):
return [thing for thing in self.things if isinstance(thing, Person)]
This doesn't really solve your initial problem, but at least it's easier and cleaner to implement for new classes - if a list attribute does not exist, it's because you haven't implemented the method for it.
Use hasattr, setattr and getattr
Use these functions on the __init__ method to programatically check if each list exists, create them if needed and append each instance to the corresponding list:
class House:
def __init__(self,*argv):
for arg in argv:
name = arg.__class__.__name__.lower()
if not hasattr(self, name):
setattr(self, name, [])
getattr(self, name).append(arg)
I personally think this is worse, since your attributes will be named exactly like the class name (i.e. person, not people), and you can't clearly see which lists are initialized as attributes or not since it's done on the fly, but it should work for your described use case.
Whichever way you decide to go with, note that I personally feel like your design isn't very effective for dealing with this problem. I'd rather create empty lists for people, pets etc on the House's __init__ method and add specific methods like add_person, add_pet, add_group etc for appending objects to the House's list attributes. It may not seem like much, but this design:
a) clearly defines supported classes that can interact with the House class; and
b) lets you see more clearly exactly who is getting put into the House, since you need to explicitly call the method in order to do so.
I have restructured your code. Check it out :)
class House:
def __init__(self,*argv):
self.house_classes = {"people": Person, "pets": Pet}
self.house_objects = {}
for object in argv:
self.add_house_object(object)
def add_house_class(self, class_id, class):
self.house_classes["class_id"] = class
def add_house_object(self, object):
for class_id in self.house_classes:
if isinstance(object, self.house_classes[class_id]):
if class_id in self.house_objects:
self.house_objects["class_name"].append(object)
return
self.house_objects["class_id"] = [object]
class Person:
def __init__(self,name):
self.name = name
class Pet:
def __init__(self,name):
self.name = name
To add new classes (e.g Doors) to a house object (as i think you want)
my_house = House(house_objects...) #initialise a house object
class Doors: #your new class to add to house object
def __init__(self,name):
self.name = name
my_house.add_house_class(self, "doors", Doors) #added to house object
new_door = Door("my door") #door object
my_house.add_house_object(new_door)
I hope that helps :)
You can check if House has a doors list with getattr(House, 'doors', None) and create the list if it is not existing. This solution assumes that you intend to create the list as a class variable (I am assuming this, since you do NOT pass any House-instance reference do Doors when instantiating a Doors instance).
class Doors:
def __init__(self,name):
if getattr(House, 'doors', None) is None:
House.doors = []
self.name = name
House.doors.append(self)
BUT I strongly advise you to NOT USE THIS PATTERN. This looks like a good case for class inheritance, for example creating the class Doors with class Doors(House):.
Furthermore I've got the feeling that you should take a look at the definitions and meanings of class variables and instance variables.
Imho the best way to deal with this task would be to make Doors a class which inherits from House and to require an existing instance of house to be able to create a Doors instance (for example check with if isinstance(new_house, House):). Then the Doors __init__ method could create and/or append a list doors as instance variable to House.
This way you can create many different houses. Whereas when using a class variable for the doors list, each house would have all doors created in all houses.
Thus I recommend using this pattern:
class Doors(House):
def __init__(self, name, house):
if not isinstance(house, House):
raise ValueError('`house` is not an existing instance of the `House` class')
if getattr(house, 'doors', None) is None:
house.doors = []
else:
print('exi')
self.name = name
house.doors.append(self)
As jfaccioni pointed out: Inheritance is not mandatory here, but this kind of construct looks like you are going to need it in the long term for method-access etc.

How do you keep code consistent with multiple developers?

I know that Python is a dynamically typed language, and that I am likely trying to recreate Java behavior here. However, I have a team of people working on this code base, and my goal with the code is to ensure that they are doing things in a consistent manner. Let me give an example:
class Company:
def __init__(self, j):
self.locations = []
When they instantiate a Company object, an empty list that holds locations is created. Now, with Python anything can be added to the list. However, I would like for this list to only contain Location objects:
class Location:
def __init__(self, j):
self.address = None
self.city = None
self.state = None
self.zip = None
I'm doing this with classes so that the code is self documenting. In other words, "location has only these attributes". My goal is that they do this:
c = Company()
l = Location()
l.city = "New York"
c.locations.append(l)
Unfortunately, nothing is stopping them from simply doing c.locations.append("foo"), and nothing indicates to them that c.locations should be a list of Location objects.
What is the Pythonic way to enforce consistency when working with a team of developers?
An OOP solution is to make sure the users of your class' API do not have to interact directly with your instance attributes.
Methods
One approach is to implement methods which encapsulate the logic of adding a location.
Example
class Company:
def __init__(self, j):
self.locations = []
def add_location(self, location):
if isinstance(location, Location):
self.locations.append(location)
else:
raise TypeError("argument 'location' should be a Location object")
Properties
Another OOP concept you can use is a property. Properties are a simple way to define getter and setters for your instance attributes.
Example
Suppose we want to enforce a certain format for a Location.zip attribute
class Location:
def __init__(self):
self._zip = None
#property
def zip(self):
return self._zip
#zip.setter
def zip(self, value):
if some_condition_on_value:
self._zip = value
else:
raise ValueError('Incorrect format')
#zip.deleter
def zip(self):
self._zip = None
Notice that the attribute Location()._zip is still accessible and writable. While the underscore denotes what should be a private attribute, nothing is really private in Python.
Final word
Due to Python's high introspection capabilities, nothing will ever be totally safe. You will have to sit down with your team and discuss the tools and practice you want to adopt.
Nothing is really private in python. No class or class instance can
keep you away from all what's inside (this makes introspection
possible and powerful). Python trusts you. It says "hey, if you want
to go poking around in dark places, I'm gonna trust that you've got a
good reason and you're not making trouble."
After all, we're all consenting adults here.
--- Karl Fast
You could also define a new class ListOfLocations that make the safety checks. Something like this
class ListOfLocations(list):
def append(self,l):
if not isinstance(l, Location): raise TypeError("Location required here")
else: super().append(l)

Attribute name change for inherited classes. Possible/Bad practice?

Q1. If I have a very general class, with an attribute whose name could be better represented in more specific inherited classes, how can I access the same methods from the parent class if the attribute has changed its name? For example (not my real scenario, but it shows what I mean).
class Entity(object):
def __init__(self):
self.members= {}
... # Methods that use self.members
class School(Entity):
def __init__(self):
super(Entity,self).__init__(self)
class Company(Entity):
def __init__(self):
super(Entity,self).__init__(self)
for class School and for class Company, I would like to be able to use attributes that are more specific, such as self.students and self.employees, but that still work with the methods that were defined for self.members in the class Entity.
Q2. Would this be bad practice? What would be the best way to approach this? In my real case, the word I used for self.members is too general.
Renaming an attribute in a subclass is bad practice in general.
The reason is that inheritance is about substitutability. What it means for a School to be an Entity is that you can use a School in any code that was written to expect an Entity and it will work properly.
For example, typical code using an Entity might do something like this:
for member in entity.members:
If you have something that claims to be an Entity (and even passes isinstance(myschool, Entity)), but it either doesn't have members, or has an empty members, because its actual members are stored in some other attribute, then that code is broken.
More generally, if you change the interface (the set of public methods and attributes) between a base class and dericed class, the derived class isn't a subtype, which means it usually shouldn't be using inheritance in the first case.1
If you make students into an alias for members, so the same attribute can be accessed under either name, then you do have a subtype: a School has its students as members, and therefore it can be sensibly used with code that expects an Entity:
myschool.students.append(Person(cap'))
# ...
for member in myschool.members:
# now cap is going to show up here
And this works just as well with methods defined in Entity:
def slap_everyone(self):
for member in self.members:
# this will include cap
member.slap()
myschool.slap_everyone()
And you can do this by using #property.
class Student(Entity):
# ...
#property
def students(self):
return members
#students.setter
def students(self, val):
self.members = val
#students.deleter
def students(self):
del self.members
So, this isn't flat-out invalid or anything.
But it is potentially misleading.
Will it be obvious to readers of your code that adding cap to myschool.students is going to add him to myschool.members? If so, it's probably OK. If not, or if you're not sure, then you probably shouldn't do this.
Another thing to consider is that a School might have multiple kinds of members: students, teachers, administrators, dropouts who hang around their old campus because they don't know where else to find drug dealers, … If that's part of your design, then what you really want is for members to be a property, and probably a read-only property at that,2 and each subclass can define what counts as "members" in a way that makes sense for that subclass.
class Entity(object):
#property
def members(self):
return []
def rollcall(self):
return ', '.join(self.members)
class School(Entity):
def __init__(self):
super(School, self).__init__()
self.students, self.teachers = [], []
#property
def members(self):
return self.students + self.teachers
school = School()
school.teachers.append('cap')
school.students.extend(['marvel', 'america, planet'])
print(school.rollcall())
This will print out:
cap, marvel, america, planet
That school is working as a School, and as an Entity, and everything is good.
1. I say usually because (regardless of what OO dogma says) there are other reasons for subclassing besides subtyping. But it's still the main reason. And in this case, there doesn't appear to be any other reason for subclassing—you're not trying to share storage details, or provide overriding hooks, or anything like that.
2. In fact, you might even want to drag in the abc module and make it an abstract property… but I won't show that here.

Subclass avoiding parent's metaclass

Say I have a third-party library where a metaclass requires me to implement something. But I want to have an intermediate "abstract" subclass that doesn't. How can I do this?
Consider this to be a very minimal example of what third-party library has:
class ServingMeta(type):
def __new__(cls, name, bases, classdict):
if any(isinstance(b, ServingMeta) for b in bases):
if "name" not in classdict:
# Actual code fails for a different reason,
# but the logic is the same.
raise TypeError(f"Class '{name}' has no 'name' attribute")
return super().__new__(cls, name, bases, classdict)
class Serving(object, metaclass=ServingMeta):
def shout_name(self):
return self.name.upper()
I cannot modify the code above. It's an external dependency (and I don't want to fork it).
The code is meant to be used this way:
class Spam(Serving):
name = "SPAM"
spam = Spam()
print(spam.shout_name())
However, I happen to have a lot of spam, and I want to introduce a base class with the common helper methods. Something like this:
class Spam(Serving):
def thrice(self):
return " ".join([self.shout_name()] * 3)
class LovelySpam(Spam):
name = "lovely spam"
class WonderfulSpam(Spam):
name = "wonderful spam"
Obviously, this doesn't work and fails with the well-expected TypeError: Class 'SpamBase' has no 'name' attribute declared. Would third-party library had a SpamBase class without a metaclass, I could've subclassed that - but no such luck this time (I've mentioned this inconvenience to the library authors).
I can make it a mixin:
class SpamMixin(object):
def thrice(self):
return " ".join([self.shout_name()] * 3)
class LovelySpam(SpamMixin, Serving):
name = "lovely spam"
class WonderfulSpam(SpamMixin, Serving):
name = "wonderful spam"
However, this makes me and my IDE cringe a little, as it quickly becomes cumbersome to repeat SpamMixin everywhere and also because object has no shout_name attribute (and I don't want to silence analysis tools). In short, I just don't like this approach.
What else can I do?
Is there a way to get a metaclass-less version of Serving? I think of something like this:
ServingBase = remove_metaclass(Serving)
class Spam(ServingBase, metaclass=ServingMeta):
...
But don't know how to actually implement remove_metaclass and whenever it's even reasonably possible (of course, it must be doable, with some introspection, but it could require more arcane magic than I can cast).
Any other suggestions are also welcomed. Basically, I want to have my code DRY (one base class to rule them all), and have my linter/code analysis icons all green.
The mixin approach is the correct way to go. If your IDE "cringe" that is a deffect on that tool - just disable a little of the "features" that are obviously incorrect tunning when coding for a dynamic language like Python.
And this is not even about creating things dynamically, it is merely multiple-inheritance, which is supported by the language since forever. And one of the main uses of multiple-inheritance is exactly being able to create mixins just as this one you need.
Another inheritance-based workaround is to make your hierarchy one level deeper, and just introduce the metaclass after you come up with your mixin methods:
class Mixin(object):
def mimixin(self): ...
class SpamBase(Mixin, metaclass=ServingMeta):
name = "stub"
Or just addd the mixin in an intermediate subclass:
class Base(metaclass=Serving Meta):
name = "stub"
class MixedBase(Mixin, Base):
name = "stub"
class MyLovingSpam(MixedBase):
name = "MyLovingSpam"
If you don't want to be repeating the mixin=-base name in every class, that is the way to go.
"Removing" a metaclass just for the sake of having a late mixin is way over the top. Really. Broken. The way to do it wol e re-create the class dynamically, just as #vaultah mentions in the other answer, but doing that in an intermediate class is a thing you should not do. Doing that to please the IDE is something you should not do twice: messing with metaclasses is hard enough already. Removing things on inheritance/class creation that the language puts there naturally is something nasty (cf. this answer: How to make a class attribute exclusive to the super class ) . On the other hand, mixins and multiple inheritance are just natural.
Are you still there? I told you not to do so:
Now, onto your question - instead of "supressing the metaclass" in an intermediate class, it would be more feasible to inherit the metaclass you have there and change its behavior - so that it does not check for the constraints in specially marked classes - create an attribute for your use, like _skip_checking
class MyMeta(ServingMeta):
def __new__(metacls, name, bases, namespace):
if namespace.get("_skip_checking", False):
# hardcode call to "type" metaclass:
del namespace["_skip_checking"]
cls = type.__new__(metacls, name, bases, namespace)
else:
cls = super().__new__(metacls, name, bases, namespace)
return cls
# repeat for __init__ if needed.
class Base(metaclass=MyMeta):
_skip_checking = True
# define mixin methods
class LoveSpam(Base):
name = "LoveSpam"
There's really no direct way to remove the metaclass from a Python class, because the metaclass created that class. What you can try is re-create the class using a different metaclass, which doesn't have unwanted behaviour. For example, you could use type (the default metaclass).
In [6]: class Serving(metaclass=ServingMeta):
...: def shout_name(self):
...: return self.name.upper()
...:
In [7]: ServingBase = type('ServingBase', Serving.__bases__, dict(vars(Serving)))
Basically this takes the __bases__ tuple and the namespace of the Serving class, and uses them to create a new class ServingBase. N.B. this means that ServingBase will receive all bases and methods/attributes from Serving, some of which may have been added by ServingMeta.

Is there a benefit to defining a class inside another class in Python?

What I'm talking about here are nested classes. Essentially, I have two classes that I'm modeling. A DownloadManager class and a DownloadThread class. The obvious OOP concept here is composition. However, composition doesn't necessarily mean nesting, right?
I have code that looks something like this:
class DownloadThread:
def foo(self):
pass
class DownloadManager():
def __init__(self):
dwld_threads = []
def create_new_thread():
dwld_threads.append(DownloadThread())
But now I'm wondering if there's a situation where nesting would be better. Something like:
class DownloadManager():
class DownloadThread:
def foo(self):
pass
def __init__(self):
dwld_threads = []
def create_new_thread():
dwld_threads.append(DownloadManager.DownloadThread())
You might want to do this when the "inner" class is a one-off, which will never be used outside the definition of the outer class. For example to use a metaclass, it's sometimes handy to do
class Foo(object):
class __metaclass__(type):
....
instead of defining a metaclass separately, if you're only using it once.
The only other time I've used nested classes like that, I used the outer class only as a namespace to group a bunch of closely related classes together:
class Group(object):
class cls1(object):
...
class cls2(object):
...
Then from another module, you can import Group and refer to these as Group.cls1, Group.cls2 etc. However one might argue that you can accomplish exactly the same (perhaps in a less confusing way) by using a module.
I don't know Python, but your question seems very general. Ignore me if it's specific to Python.
Class nesting is all about scope. If you think that one class will only make sense in the context of another one, then the former is probably a good candidate to become a nested class.
It is a common pattern make helper classes as private, nested classes.
There is another usage for nested class, when one wants to construct inherited classes whose enhanced functionalities are encapsulated in a specific nested class.
See this example:
class foo:
class bar:
... # functionalities of a specific sub-feature of foo
def __init__(self):
self.a = self.bar()
...
... # other features of foo
class foo2(foo):
class bar(foo.bar):
... # enhanced functionalities for this specific feature
def __init__(self):
foo.__init__(self)
Note that in the constructor of foo, the line self.a = self.bar() will construct a foo.bar when the object being constructed is actually a foo object, and a foo2.bar object when the object being constructed is actually a foo2 object.
If the class bar was defined outside of class foo instead, as well as its inherited version (which would be called bar2 for example), then defining the new class foo2 would be much more painful, because the constuctor of foo2 would need to have its first line replaced by self.a = bar2(), which implies re-writing the whole constructor.
You could be using a class as class generator. Like (in some off the cuff code :)
class gen(object):
class base_1(object): pass
...
class base_n(object): pass
def __init__(self, ...):
...
def mk_cls(self, ..., type):
'''makes a class based on the type passed in, the current state of
the class, and the other inputs to the method'''
I feel like when you need this functionality it will be very clear to you. If you don't need to be doing something similar than it probably isn't a good use case.
There is really no benefit to doing this, except if you are dealing with metaclasses.
the class: suite really isn't what you think it is. It is a weird scope, and it does strange things. It really doesn't even make a class! It is just a way of collecting some variables - the name of the class, the bases, a little dictionary of attributes, and a metaclass.
The name, the dictionary and the bases are all passed to the function that is the metaclass, and then it is assigned to the variable 'name' in the scope where the class: suite was.
What you can gain by messing with metaclasses, and indeed by nesting classes within your stock standard classes, is harder to read code, harder to understand code, and odd errors that are terribly difficult to understand without being intimately familiar with why the 'class' scope is entirely different to any other python scope.
A good use case for this feature is Error/Exception handling, e.g.:
class DownloadManager(object):
class DowndloadException(Exception):
pass
def download(self):
...
Now the one who is reading the code knows all the possible exceptions related to this class.
Either way, defined inside or outside of a class, would work. Here is an employee pay schedule program where the helper class EmpInit is embedded inside the class Employee:
class Employee:
def level(self, j):
return j * 5E3
def __init__(self, name, deg, yrs):
self.name = name
self.deg = deg
self.yrs = yrs
self.empInit = Employee.EmpInit(self.deg, self.level)
self.base = Employee.EmpInit(self.deg, self.level).pay
def pay(self):
if self.deg in self.base:
return self.base[self.deg]() + self.level(self.yrs)
print(f"Degree {self.deg} is not in the database {self.base.keys()}")
return 0
class EmpInit:
def __init__(self, deg, level):
self.level = level
self.j = deg
self.pay = {1: self.t1, 2: self.t2, 3: self.t3}
def t1(self): return self.level(1*self.j)
def t2(self): return self.level(2*self.j)
def t3(self): return self.level(3*self.j)
if __name__ == '__main__':
for loop in range(10):
lst = [item for item in input(f"Enter name, degree and years : ").split(' ')]
e1 = Employee(lst[0], int(lst[1]), int(lst[2]))
print(f'Employee {e1.name} with degree {e1.deg} and years {e1.yrs} is making {e1.pay()} dollars')
print("EmpInit deg {0}\nlevel {1}\npay[deg]: {2}".format(e1.empInit.j, e1.empInit.level, e1.base[e1.empInit.j]))
To define it outside, just un-indent EmpInit and change Employee.EmpInit() to simply EmpInit() as a regular "has-a" composition. However, since Employee is the controller of EmpInit and users don't instantiate or interface with it directly, it makes sense to define it inside as it is not a standalone class. Also note that the instance method level() is designed to be called in both classes here. Hence it can also be conveniently defined as a static method in Employee so that we don't need to pass it into EmpInit, instead just invoke it with Employee.level().

Categories

Resources