Scala -- the use of trait trait

Keywords: Scala Big Data Hadoop

1. Introduction to characteristics

1.1 general

Sometimes, we will encounter some specific needs, that is, to strengthen the functions of some classes (or objects) without affecting the current inheritance system. For example, there are monkeys and elephants. They all have names, ages and eating functions, but some monkeys are trained by the circus, Learned to ride a unicycle. The function of riding a unicycle should not be defined in the parent class (animal class) or monkey class, but in the trait. The trait in Scala should be modified with the keyword trait

1.2 features

  • Characteristics can improve the reusability of code

  • Characteristics can improve code scalability and maintainability

  • There is an inheritance relationship between classes and traits, but only single inheritance is supported between classes, but between classes and traits, either single inheritance or multiple inheritance is allowed

  • Scala can have ordinary fields, abstract fields, ordinary methods and abstract methods

    be careful:

    1. If there is only abstract content in the trait, such trait is called thin interface
    2. If there is both abstract content and concrete content in the trait, it is called rich interface

1.3 syntax

Defining traits

trait Trait name {
    // General field
    // Abstract field
    
    // Common method
    // Abstract method
}

Inherited traits

class class extends Trait 1 with Trait 2 {
    // Override abstract field
    // Override abstract methods
}

be careful

  • In scala, whether it is a class or a trait, the inheritance relationship uses the extends keyword
  • If you want to inherit multiple traits, the trait names are separated by the with keyword

1.4 example: class inherits a single trait

demand

  1. Create a Logger and add a log(msg:String) method
  2. Create a ConsoleLogger class, inherit the Logger characteristics, implement the log method, and print messages
  3. Add the main method, create the ConsoleLogger object, and call the log method

Reference code

//Case: inheriting a single Trait such as the introduction to trail
object ClassDemo01 {
  //1. Define a trait
  trait Logger {
    def log(msg:String)   //Abstract method
  }

  //2. Define a class and inherit characteristics
  class ConsoleLogger extends Logger {
    override def log(msg: String): Unit = println(msg)
  }

  def main(args: Array[String]): Unit = {
    //3. Call the method in the class
    val cl = new ConsoleLogger
    cl.log("trait introduction: Class inherits a single trait")
  }
}

1.5 example: a class inherits multiple trait s

demand

  1. Create a MessageSender attribute and add the send(msg:String) method
  2. Create a MessageReceiver and add the receive() method
  3. Create a MessageWorker class, inherit these two characteristics, and override the above two methods
  4. Test in main and call send method and receive method respectively

Reference code

//Case: a class inherits multiple trait s
object ClassDemo02 {
  //1. Define a characteristic: MessageSender, which means to send messages
  trait MessageSender {
    def send(msg:String)
  }
  //2. Define a feature: MessageReceiver, which means receiving information
  trait MessageReceiver {
    def receive()
  }
  //3. Define a MessageWorker class that inherits two characteristics
  class MessageWorker extends MessageSender with MessageReceiver {
    override def send(msg: String): Unit = println("send message: " + msg)

    override def receive(): Unit = println("Message received, I'm fine, thank you!...")
  }

  //Main method as the main entry of the program
  def main(args: Array[String]): Unit = {
    //4. Call the method in the class
    val mw = new MessageWorker
    mw.send("Hello, How do you do!")
    mw.receive()
  }
}

1.6 example: object inherits trait

demand

  1. Create a Logger and add a log(msg:String) method
  2. Create a Warning feature and add a warn(msg:String) method
  3. Create a singleton object ConsoleLogger, inherit the Logger and Warning attributes, and rewrite the abstract methods in the attributes
  4. Write the main method and call the log and warn methods of the singleton object ConsoleLogger

Reference code

//Case: demonstrate the inheritance characteristics of object singleton
object ClassDemo03 {
  //1. Define a trait Logger and add the log(msg:String) method
  trait Logger{
    def log(msg:String)
  }

  //2. Define a Warning and add a warn(msg:String) method
  trait Warning{
    def warn(msg:String)
  }

  //3. Define the singleton object ConsoleLogger, inherit the above two characteristics, and override the two methods
  object ConsoleLogger extends Logger with Warning{
    override def log(msg: String): Unit = println("Console log information: "  + msg)

    override def warn(msg: String): Unit = println("Console warning message: " + msg)
  }

  //main method as the entry of the program
  def main(args: Array[String]): Unit = {
    //4. Call two methods in the ConsoleLogger singleton object
    ConsoleLogger.log("I am an ordinary log message!")
    ConsoleLogger.warn("I am a warning log message!")
  }
}

1.7 example: demonstrating members in trait

demand

  1. Define a trait Hero, add the concrete field name, the abstract field arms, the concrete method eat(), and the abstract method toWar()
  2. Define a class general, inherit the Hero trait, and override all abstract members
  3. In the main method, create an object of the general class and call its members

Reference code

//Case: demonstrate the members in the trait
object ClassDemo04 {
  //1. Define a trait Hero
  trait Hero{
    var name = ""     //Specific field
    var arms:String   //Abstract field

    //Specific method
    def eat() = println("Eating meat and drinking, recuperate and build up energy!")

    //Abstract method
    def toWar():Unit
  }

  //2. Define a class general, inherit the Hero trait, and rewrite all abstract members
  class Generals extends Hero {
    //Override abstract fields in parent attributes
    override var arms: String = ""

    //Override abstract methods in parent attributes
    override def toWar(): Unit = println(s"${name}With ${arms}, go to the front to fight the enemy!")
  }

  //main method as the entry of the program
  def main(args: Array[String]): Unit = {
    //3. Create an object of the general class
    val gy = new Generals

    //4. Test the contents of the general class
    //Assign values to member variables
    gy.name = "Guan Yu"
    gy.arms = "falchion"
    //Print member variable values
    println(gy.name, gy.arms)
    //Call member method
    gy.eat()
    gy.toWar()
  }
}

2. Object blending trait

Sometimes, we want to temporarily enhance or extend the functions of objects without changing the class inheritance system. At this time, we can consider using object blending technology. The so-called object blending refers to that in scala, there is no inheritance relationship between classes and traits, but through specific keywords, You can make this kind of object have members in a specified trait

2.1 syntax

val/var Object name = new class with Idiosyncrasy

2.2 example

demand

  1. Create a Logger attribute and add a log(msg:String) method
  2. Create a User class that has no relationship with the Logger attribute
  3. Test the log() method in the main method, which makes the object of User class have Logger characteristics through object blending technology

Reference code

//Case: demonstrating dynamic blending
object ClassDemo05 {
  //1. Create a Logger attribute and add a log(msg:String) method
  trait Logger {
    def log(msg:String) = println(msg)
  }

  //2. Create a User class, which has no task relationship with the Logger attribute
  class User

  //main method as the entry of the program
  def main(args: Array[String]): Unit = {
    //3. Test the log() method in the main method to make the object of User class have Logger characteristics through object blending technology
    val c1 = new User with Logger     //Object blending
    c1.log("I am User Object of class, I can call Logger In trait log Method")
  }
}

3. Use trait to implement the adapter mode

3.1 introduction to design mode

summary

Design Pattern is the predecessors' summary of code development experience and a series of routines to solve specific problems. It is not a syntax specification, but a set of solutions to improve code reusability, maintainability, readability, robustness and security.

classification

There are 23 design modes, which are divided into the following three categories:

  1. Create type

    It refers to the object that needs to be created. Common modes include: Singleton mode and factory method mode

  2. Structural type

    It refers to the relationship architecture between classes and traits. The commonly used modes are adapter mode and decoration mode

  3. Behavioral type

    It refers to what a class (or trait) can do. Common modes include template method mode and responsibility chain mode

3.2 adapter mode

When there are multiple abstract methods in a trait and we only need to use one or more of them, we have to rewrite all the abstract methods in the trait, which is very troublesome. In this case, we can define an abstract class to inherit the trait and rewrite all the abstract methods in the trait, and the method body is empty, We only need to define the class, inherit the abstract class and override the specified method. This abstract class is called adapter class. This design pattern (design idea) is called adapter design pattern

structure

trait Idiosyncrasy A{
    //Abstract method 1
    //Abstract method 2
    //Abstract method 3
    //...
}
abstract class class B extends A{	//adapter class 
    //Override abstract method 1, method body is empty
    //Override abstract method 2, method body is empty
    //Override abstract method 3, method body is empty
    //...
}
class Custom class C extends class B {
    //You can override which method you need to use
}

demand

  1. Define PlayLOL and add 6 abstract methods: top(), mid(), adc(), support(), jungle(), schoolchild()

    Explanation: top: on order, mid: medium order, adc: off road, support: auxiliary, jungle: playing wild, schoolchild: primary school students

  2. Define the abstract class Player, inherit the PlayLOL feature, override all abstract methods in the feature, and the method body is empty

  3. Define common class GreenHand, inherit Player, override support() and schoolchild() methods

  4. Define the main method, create the object of GreenHand class in it, and call its method to test

Reference code

//Case: demonstrate the adapter design pattern
object ClassDemo06 {
  //1. Define PlayLOL and add 6 abstract methods: top(), mid(), adc(), support(), jungle(), schoolchild()
  trait PlayLOL {
    def top()           //top
    def mid()           //mid
    def adc()           //Down the road
    def support()       //auxiliary
    def jungle()        //Fight wild
    def schoolchild()   //pupil
  }

  //2. Define the abstract class Player, inherit the PlayLOL feature, rewrite all abstract methods in the feature, and the method body is empty
  //The role of the Player class is the adapter class
  class Player extends PlayLOL {
    override def top(): Unit = {}
    override def mid(): Unit = {}
    override def adc(): Unit = {}
    override def support(): Unit = {}
    override def jungle(): Unit = {}
    override def schoolchild(): Unit = {}
  }

  //3. Define the common class GreenHand, inherit Player, and override the support() and schoolchild() methods
  class GreenHand extends Player{
    override def support(): Unit = println("I'm an assistant, B Key button, Don't die, don't go back to town!")
    override def schoolchild(): Unit = println("I'm a pupil, You scold me, I'll hang up!")
  }

  //4. Define the main method, create the object of GreenHand class in it, and call its method for testing
  def main(args: Array[String]): Unit = {
    //Create an object of the GreenHand class
    val gh = new GreenHand
    //Call a method in the GreenHand class
    gh.support()
    gh.schoolchild()
  }
}

4. Use trait to implement template method pattern

In real life, we will encounter paper templates, resume templates, including some templates in PPT. In the process of object-oriented programming, programmers often encounter this situation: when designing a system, they know the key steps required by the algorithm and determine the execution order of these steps, but the specific implementation of some steps is still unknown, In other words, the implementation of some steps is related to the specific environment.

For example, when you go to a bank to handle business, you generally go through the following four processes: number retrieval, queuing, handling specific business, scoring bank staff, etc. the businesses of number retrieval, queuing and scoring bank staff are the same for each customer and can be implemented in the parent class, but the specific business varies from person to person. It may be deposit, withdrawal or transfer, Implementation can be deferred to subclasses. This requires the use of template design patterns

4.1 General

In Scala, we can first define an algorithm skeleton in operation, and delay some steps of the algorithm to subclasses, so that subclasses can redefine some specific steps of the algorithm without changing the structure of the algorithm, which is: template method design pattern

advantage

  1. More expansibility

    The public part is encapsulated in the parent class, and the variable part is implemented by the child class

  2. Comply with the opening and closing principle.

    Some methods are implemented by subclasses, so subclasses can add corresponding functions by extension

shortcoming

  1. The increase in the number of classes leads to a larger system and more abstract design.

    Because you need to define a subclass for each different implementation

  2. It improves the difficulty of code reading.

    Abstract methods in the parent class are implemented by subclasses, and the execution results of subclasses will affect the results of the parent class, which leads to a reverse control structure

4.2 format

class A {		//The parent class encapsulates the public part
    def Method name(parameter list) = {	//The specific method is also called template method
        //Step 1, known
       	//Step 2, unknown, call the abstract method
        //Step 3, known
        //Step n
    }
    
    //Abstract method
}

class B extends A {
    //Override abstract methods
}

Note: the number of abstract methods should be determined according to the specific requirements, not necessarily only one, but also multiple

4.3 example

demand

  1. Define a Template class Template and add code() and getRuntime() methods to get the execution time of some code
  2. Define the class ForDemo to inherit the Template, and then override the code() method to calculate the execution time of "Hello,Scala!" printed 10000 times
  3. Define the main method to test the specific execution time of the code

Reference code

//Case: demonstrate template method design pattern
object ClassDemo07 {

  //1. Define a Template class Template and add code() and getRuntime() methods to obtain the execution time of some codes
  abstract class Template {
    //Define the code() method to record all the code to be executed
    def code()

    //Define template methods to get the execution time of some code
    def getRuntime() = {
      //Gets the current time in milliseconds
      val start = System.currentTimeMillis()
      //Specific code to execute
      code()
      //Gets the current time in milliseconds
      val end = System.currentTimeMillis()
      //Returns the execution time of the specified code
      end - start
    }
  }

  //2. Define the class ForDemo to inherit the Template, and then override the getRuntime() method to calculate the execution time of printing "Hello,Scala!" 10000 times
  class ForDemo extends Template {
    override def code(): Unit = for(i <- 1 to 10000) println("Hello, Scala!")
  }

  def main(args: Array[String]): Unit = {
    //3. Test the execution time of printing "Hello, Scala!" 10000 times
    println(new ForDemo().getRuntime())
  }
}

5. Use trait to realize the responsibility chain mode

5.1 general

The same method appears in multiple traits, and the method finally calls super. The method name (). When the class inherits the multiple traits, it can call the same method in multiple traits in turn, forming a call chain.

The execution sequence is:

  1. Follow the sequence from right to left

    That is, first, the right most trait method will be executed, and then the methods in the corresponding trait will be executed to the left in turn

  2. When the method of all child traits is executed, the method of the parent trait will be executed finally

This design idea is called responsibility chain design pattern

Note: in Scala, the case where a class inherits multiple traits is called superposition traits

5.2 format

trait A {			//Paternity
    def show()		//The hypothetical method is called show
}

trait B extends A {	//You can define multiple sub characteristics according to your needs
    override def show() = {
        //Specific code logic
        super.show()
    }
}

trait C extends A {
    override def show() = {
        //Specific code logic
        super.show()
    }
}

class D extends B with C {	//Specific classes are used to demonstrate: superposition characteristics
     def Method name() = {		  //This can be the class's own method, not necessarily the show() method
        //Specific code logic
        super.show()		//This constitutes the call chain
    }
}
/*
	The execution sequence is:
		1. First execute your own method in class D
		2. Then execute the show() method in C
		3. Then execute the show() method in Figure B
		4. Finally, execute the show() method in A
*/

5.3 example

demand

Through Scala code, a call chain simulating the payment process is realized

Explanation:

If we want to develop a payment function, we often need to perform a series of verification to complete the payment. For example:

  1. Perform payment signature verification
  2. Data validity verification
  3. ...

If more verification rules need to be added due to the adjustment of third-party interface payment in the future, how can the previous verification code not be modified to realize the extension?

This requires: responsibility chain design pattern

step

  1. Define a Handler feature and add a specific handle(data:String) method to represent the processing data (specific payment logic)
  2. Define a DataValidHandler trait and inherit the Handler trait
    • Overwrite the handle() method, print the validation data, and then invoke the handle() method of the parent attribute.
  3. Define a signaturevalhandler trait and inherit the Handler trait
    • Overwrite the handle() method, print "check the signature", and then invoke the handle() method of the parent attribute.
  4. Create a Payment class that inherits the DataValidHandler and signaturevalhandler attributes
    • Define the pay(data:String) method, print the "user initiated payment request", and then call the parent attribute handle() method.
  5. Add the main method, create the Payment object instance, and then call the pay() method.

Reference code

//Case: demonstrate responsibility chain mode (also known as call chain mode)
object ClassDemo08 {
  //1. Define a parent Handler to represent data processing (specific payment logic)
  trait Handler {
    def handle(data:String) = {
      println("Specific data processing code(for example: Transfer logic)")
      println(data)
    }
  }
  //2. Define a subdatavalidhandler to represent the verification data
  trait DataValidHandler extends Handler {
    override def handle(data:String) = {
      println("Check data...")
      super.handle(data)
    }
  }
  //3. Define a sub signature validhandler, which represents the verification signature
  trait SignatureValidHandler extends Handler {
    override def handle(data:String) = {
      println("Verification signature...")
      super.handle(data)
    }
  }
  //4. Define a class Payment, which represents the Payment request initiated by the user
  class Payment extends DataValidHandler with SignatureValidHandler {
    def pay(data:String) = {
      println("User initiated payment request...")
      super.handle(data)
    }
  }
  def main(args: Array[String]): Unit = {
    //5. Create the object of Payment class and simulate: call chain
    val pm = new Payment
    pm.pay("Su Mingyu transferred 10000 yuan to Su Daqiang")
  }
}

// The program operation output is as follows:
// User initiated payment request
// Verify signature
// Check data
// Specific data processing code (e.g. transfer logic)
// Su Mingyu transferred 10000 yuan to Su Daqiang

6. Construction mechanism of trait

6.1 general

If a class inherits a parent class and inherits multiple parent traits, how is the class (subclass), its parent class, and its parent traits constructed?

To solve this problem, we need to use the construction mechanism of trait

6.2 construction mechanism rules

  • Each trait has only * * one parameterless * * constructor.

    In other words, trait also has construction code, but unlike classes, traits cannot have constructor parameters

  • When one class inherits another class and multiple trait s, when creating an instance of this class, its constructor execution order is as follows:

    1. Constructor that executes the parent class
    2. Execute the constructor of the trait from left to right
    3. If a trait has a parent trait, the constructor of the parent trait is executed first
    4. If multiple traits have the same parent trait, the constructor of the parent trait is initialized only once
    5. Execute subclass constructor

6.3 examples

demand

  • Define a parent class and multiple attributes, and then inherit them with a class
  • Create subclass objects and test the construction order of the trait

step

  1. Create a Logger and print "execute Logger constructor!" in the constructor
  2. Create a MyLogger trait, inherit from the Logger trait, and print "execute MyLogger constructor!" in the constructor
  3. Create a TimeLogger trait, inherit from the Logger trait, and print "execute TimeLogger constructor!" in the constructor
  4. Create the Person class and print "execute Person constructor!" in the constructor
  5. Create a Student class, inherit the Person class, mylogger and timelogge characteristics, and print "execute Student constructor" in the constructor
  6. Add the main method, create the object of Student class, and observe the output.

Reference code

//Case: demonstrate the construction mechanism of trait
object ClassDemo09 {
  //1. Create a Logger parent
  trait Logger {
    println("implement Logger constructor ")
  }
  //2. Create a MyLogger child trait and inherit the Logger trait
  trait MyLogger extends Logger {
    println("implement MyLogger constructor ")
  }
  //3. Create a TimeLogger child trait and inherit the Logger trait
  trait TimeLogger extends Logger {
    println("implement TimeLogger constructor ")
  }
  //4. Create the parent class Person
  class Person{
    println("implement Person constructor ")
  }
  //5. Create a subclass Student, inherit the Person class and the characteristics of TimeLogger and MyLogger
  class Student extends Person with TimeLogger with MyLogger {
    println("implement Student constructor ")
  }
  //main method, the entry of the program
  def main(args: Array[String]): Unit = {
    //6. Create the object of Student class and observe the output.
    new Student
  }
}

// The program operation output is as follows:
// Execute the Person constructor
// Execute Logger constructor
// Execute the TimeLogger constructor
// Execute the MyLogger constructor
// Execute Student constructor

7. trait inherits class

7.1 general

In Scala, traits can also inherit classes. The trait will inherit all the members in the class.

7.2 format

class class A {			//Class A
    //Member variable
    //Member method
}

trait B extends A {	//Trait B    
}

7.3 examples

demand

  1. Define the Message class. Add the printMsg() method to print "learn Scala well and don't be afraid to go anywhere!"
  2. Create a Logger attribute and inherit the Message class
  3. Define the ConsoleLogger class and inherit the Logger characteristics
  4. In the main method, create an object of the ConsoleLogger class and call the printMsg() method

Reference code

//Case: demonstrating trait inheritance class
object ClassDemo10 {
  //1. Define Message class. Add printMsg() method to print "test data..."
  class Message {
    def printMsg() = println("learn from good examples Scala, I'm not afraid to go anywhere!")
  }
  //2. Create the Logger attribute and inherit the Message class
  trait Logger extends Message
  //3. Define the ConsoleLogger class and inherit the Logger characteristics
  class ConsoleLogger extends Logger

  def main(args: Array[String]): Unit = {
    //4. Create the object of the ConsoleLogger class and call the printMsg() method
    val cl = new ConsoleLogger
    cl.printMsg()
  }
}

8. Case: Programmer

8.1 requirements

In real life, there are many programmers, such as Python programmers and Java programmers. They all have names and ages, have to eat and have their own skills. The difference is that some Java programmers and python programmers come to dark horse programmers for training and learning, master big data technology and achieve better employment. Please use what they have learned to simulate the scene

8.2 purpose

  • Examine the relevant content of traits and abstract classes

    Simple memory: the common content of the whole inheritance system is defined into the parent class, and the extended content of the whole inheritance system is defined into the parent trait

Posted by Trekx on Sun, 28 Nov 2021 17:04:54 -0800