kotlin object oriented programming

Definitions

  • Objects: We use objects to identify things in the real world we want to model in a computer program.
  • State: Objects often have a state, which describes the characteristics each object has to be able to handle the tasks it is supposed to perform.
  • Class: A class characterizes all possible objects of some kind. It is thus an abstraction, and any such object characterized by the class is said to belong to that particular class.
  • Instance: An instance of a class represents exactly one object belonging to that class. The process of creating an object from a class and concrete data is called instantiation.
  • Construction: The process of creating an instance from a class is also called construction.
  • Property: The state of an object consists of a set of properties. Properties thus are elements of a state.
  • Method: A method is a way to access an object. This describes the functional aspects an object needs to exhibit to handle the tasks it is supposed to perform, possibly including changing and querying its state.

Class declaration in kotlin

In Kotlin, to declare a class you basically write

class ClassName(Parameter-Declaration1, Parameter-Declaration2, …) { [Class-Body]

}

Let us examine its parts:

  • ClassName: This is the name of the class. It must not contain spaces, and in Kotlin by convention you should use CamelCase notation; that is, start with a capital letter, and instead of using spaces between words, capitalize the first letter of the second word, as in EmployeeRecord.
  • Parameter-Declaration: These declare a primary constructor and describe data that are needed to instantiate classes. We talk more about parameters and parameter types later, but for now we mention that such parameter declarations basically come in three varieties:
  • Variable-Name:Variable-Type: An example would be userName: String. Use this to pass a parameter usable for instantiating a class. This happens inside a special construct called an init{} block. We’ll talk about that initialization later.
  • val Variable-Name:Variable-Type (e.g., val userName: String): Use this to pass a usable parameter from inside the init{} block, but also define a nonmutable (unchangeable) property. This parameter is thus used to directly set parts of the object’s state.
    • var Variable-Name:Variable-Type (e.g., var userName: String): Use this to pass a usable parameter from inside the init() function, but also to define a mutable (changeable) prop- erty for setting parts of the object’s state.

For the names use CamelCase notation, this time starting with a lowercase letter, as in nameOfBuyer. There are lots of possibilities for a variable type. For example, you can use Int for an integer and the declaration then could look like val a:Int.

  • [Class-Body]: This is a placeholder for any number of functions and additional properties, and also init { … } blocks worked through while instantiating a class. In addition, you can also have secondary constructors and companion objects and inner classes.

Property declaration

val Variable-Name:Variable-Type = value

for immutable properties, and

var Variable-Name:Variable-Type = value

for mutable properties. The = value is not needed, however, if the variable’s value gets set inside an init { } block.

class ClassName(Parameter-Declaration1, Parameter-Declaration2, …) {

val propertyName:PropertyType = [init-value] var propertyName:PropertyType = [init-value]

}

One word about mutability is in order: Immutable means the val variable gets its

value at some place and cannot be changed afterward, whereas mutable means the var variable is freely changeable anywhere. Immutable variables have some advantages concerning program stability, so as a rule of thumb you should always prefer immutable over mutable variables.

Class initialization

An init { } block inside the class body may contain statements that get worked through when the class gets instantiated. As the name says, it should be used to initialize instances before they actually get used. This includes preparing the state of an instance so it is set up properly to do its work. You can in fact have several init{ } blocks inside

a class. In this case the init{ } blocks get worked through sequentially in the order in

which they appear in the class. Such init{ } blocks are optional, however, so in simple cases it is totally acceptable to not provide one.

class ClassName(Parameter-Declaration1, Parameter-Declaration2, …) {

init {

// initialization actions…

}

}

If you set properties inside an init { } block, it is no longer necessary to write = [value]

inside the property declaration.

class ClassName(Parameter-Declaration1, Parameter-Declaration2, …) {

val someProperty:PropertyType

init {

someProperty = [some value]

// more initialization actions…

}

}

If you specify a property value inside the property declaration and later change the property’s value inside init { }, the value from the property declaration gets taken to initialize the property before init{ } starts. Later, inside init { } the property’s value then gets changed by suitable statements:

class ClassName {

var someProperty:PropertyType = [init-value]

init {

someProperty = [some new value]

}

}

Constructors

We already learned that parameters passed to a class when an instantiation happens get declared in parentheses after the class name:

class ClassName(Parameter-Declaration1, Parameter-Declaration2, …) {

[Class-Body]

}

We also know that parameters are accessible from inside any init{ } block and

furthermore lead to creating properties if we prepend val or var to the parameter declaration:

Variable-Name:Variable-Type

for parameters that just are needed for the init{ } blocks,

val Variable-Name:Variable-Type

if you additionally want the parameter to be converted to an immutable property, and

var Variable-Name:Variable-Type

if you additionally want the parameter to be converted to a mutable property instead.

Such a parameter declaration list in Kotlin is called a primary constructor. As you might guess, there are secondary constructors as well. Let’s talk about primary constructors first, though, because they exhibit features we haven’t seen yet.

The full primary constructor declaration actually reads:

class ClassName [modifiers] constructor( Parameter-Declaration1, Parameter-Declaration2, …)

{

[Class-Body]

}

The constructor in front of the parameter list can be omitted (together with the space

character) if there are no modifiers. As modifiers, you can add one of these visibility modifiers:

  • public: The instantiation can be done from anywhere inside and outside your program. This is the default.
  • private: The instantiation can be done only from inside the very same class or object. This makes sense if you use secondary constructors.
  • protected: The setting is the same as private, but the instantiation can be done from subclasses as well. Subclasses belong to inheritance.
  • internal: The instantiation can be done from anywhere inside the module. In Kotlin, a module is a set of files compiled together. You use this modifier if you don’t want other programs (from other projects) to access a constructor, but you otherwise want the constructor to be freely accessible from other classes or objects inside your program.

Secondary Constructors

With named parameters and default parameter values, we already have quite versatile means for various construction needs. If this is not enough for you, there is another way of describing different methods of construction: secondary constructors. You can have several of them, but their parameter list must differ from that of the primary constructor and they must also be different from each other.

To declare a secondary constructor, inside the class body write

constructor(param1:ParamType1,

param2:ParamType2, …)

{

// do some things…

}

If the class has an explicit primary constructor as well, you must delegate to a

primary constructor call as follows:

constructor(param1:ParamType1,

param2:ParamType2, …) : this(…) {

// do some things…

}

where inside this(…) the parameters for the primary constructor have to be specified. It is also possible here to specify the parameters for another secondary constructor, which in turn delegates to the primary constructor.

Interfaces : Describing a Contract

Software development is about things that need to be done, and in object-oriented development, this means things that need to be done on objects that are described inside classes. Object orientation, however, unveils a feature we haven’t talked about until now: the separation of intent and implementation. Consider, for example, a class or a couple of classes gathering information on two- dimensional graphical objects, and another class or couple of classes providing such graphical objects. This introduces a natural separation of classes. We call the information collecting part of the classes the info collector module, and the part that provides the graphical objects the client module. We want to extend that idea by allowing several client modules, and in the end we want to make sure the info collector module wouldn’t care how many clients there are .

The most important question now is this: How do the graphics objects get communicated between the modules? Here is one obvious idea: Because the clients produce the graphics objects, why not also let the clients provide the classes for them? At first that doesn’t sound bad, but there is a major drawback: The info collector module needs to know how to handle each client’s graphics object classes, and it also needs to be updated when new clients want to transfer their objects. Such a strategy is thus not flexible enough for a good program.

Let us try to turn it the other way around: The info collector module provides all graphics object classes and the clients use them to communicate data. Although this remedies the proliferation of different classes in the collector module, there is a different problem with this approach. Say, for example, the info collector gets a software update and provides an altered version for a graphics object class. If this happens, we must also update all clients, leading to a lot of work, including increased expenses in professional projects. So this approach is not the best either. What can we do?

We can introduce a new concept that does not describe how things are to be done, but only what needs to be done. This somehow mediates between the different program components and for this reason it is called an interface. If such an interface does not depend on the implementation and clients only depend on interfaces, the probability for a need to change clients is much lower if the info collector changes. You can also

consider the interface as some kind of contract between the parties: Just like in real life, if the wording in a contract is satisfied the contract is fulfilled even when the way it is done is subject to some kind of diversity.

Before I can further explain this, let’s work out the details of the graphics collector example a little more. We add the following responsibility to the graphics collector: The graphics collector must be able to take polygon objects that do the following:

  • Tell about the number of corners they have.
  • Tell us the coordinates of each corner.
  • Tell about their fill color.

You are free to extend this at will, but for our aim those three characteristics are sufficient. We now introduce an interface declaration and write this:

interface GraphicsObject {

fun numberOfCorners(): Int

fun coordsOf(index:Int): Pair<Double, Double> fun fillColor(): String

}

The Pair<Double, Double> represents a pair of floating-point numbers for the x- and y-coordinate of a point. We let the graphics collector module define the interface, because the interface is what the clients need to know from the graphics collector module to communicate with it. The implementation of the three functions is, however, exclusively the clients’ business, because for the graphics collector module the how of the contract fulfillment doesn’t matter. The interface itself, though, is just a declaration of intent, so the client modules have to define what to do to fulfill the contract. Another way of saying this is the clients have to implement the interface functions.

Structuring and Packages

For Kotlin applications it is possible to write all classes, interfaces, and singleton objects into a single file in the main folder java. Whereas for experiments and small projects this is totally acceptable, for larger projects you shouldn’t do this. Midsize to larger projects will inevitably have classes, interfaces, and singleton objects that can be grouped into modules doing different things from a bird’s-eye view perspective. Having large files implies some kind of conceptual flatness real projects don’t actually have.

For this reason Kotlin allows us to put structure units into different packages corresponding to different folders and spanning different namespaces. The first thing we need to establish is a hierarchical structure. This means we assign structure units to different nodes in a tree. Each node thus contains a couple of structure units that show high cohesion, meaning they strongly relate to each other.

A Structured Project

Let’s look at the NumberGuess example to find out what this structuring actually means. Up until now, including all the improvements and also the exercises, we have the following classes, interfaces, and singleton objects: the activity itself, a console class, a constants object, two classes and one interface for random numbers, and one class for user data. From this we identify the following packages:

  • The root for the activity class.
    • A package random for the random numbers. We put the interface right into the package, and the two implementations into a subpackage impl.
    • A gui package for the Console view element.
    • A model package for the user data class. Developers often use the term model to refer to data structures and data relations.
    • A common package for the Constants singleton object.

We put this in corresponding directories and subdirectories under src and thus get the packages and folder structure depicted in Figure 2-3.

As a convention, you must add a package declaration into each of the files reflecting this packaging structure. The syntax is:

package the.hierarchical.position

So, for example, the RandomRandom.kt file must start with

package kotlinforandroid.book.numberguess.random.impl

class RandomRandom {

}

Namespaces and Importing

As already mentioned, the hierarchical structure also spans namespaces. For example, the Console class lives by virtue of the kotlinforandroid.book.numberguess.gui package declaration in the kotlinforandroid.book.numberguess.gui namespace. This means there cannot be another Console class in the same package, but there can be Console classes in other packages, because they all have a different namespace.

Structure units (i.e., classes, interfaces, singleton objects, and companion objects)  can use other structure units from the same package by just using their simple names. If they use structure units from other packages, though, they must use their fully qualified name, which means it is necessary to prepend the package name with dots as separators. The fully qualified name for Console, for example, reads kotlinforandroid.book. numberguess.gui.Console. There is, however, a way to avoid typing lots of long names to refer to structure units from other packages: As a shortcut, you can import the referred- to structure unit by using an import statement. We have already seen that in a couple of examples, without further explaining it.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

جميع الحقوق محفوظة لموقع كيفاش 2024