Repetitive Management - From Generic Values to Generic Types and Functionals (Part 1)

Before, we talked about what computer science is , we know that computer science focuses on the management of complexity, and then in complexity management and repetitive management , we talked about an important source of complexity, which is repetitive. An important theme in software development is managing duplication, or simply, reducing duplication.

You may have heard the so-called DRY principle: Don't Repeat Yourself. Don't repeat (yourself)!

Some of the previous ones focused on theoretical aspects, and now let's look at some concrete examples.

Generic value

Some repetitions are relatively simple, and this repetition is easy for us to identify and manage. Take the frequently used "save" as an example, if we want to save student A, then write the following method:

public void saveStudentA() {
	// TODO pseudo code
	// insert A into table Student
}

Note: Unless otherwise specified, all refer to Java.

If you want to save student B again, then write another method:

public void saveStudentB() {
	// TODO pseudo code
	// insert B into table Student
}

Then, even beginners can feel that there is a huge problem in writing this way, obviously there is a lot of repetition and various hard codes .

There could be thousands of students to save, and it would be impossible to write a separate method for each saved student!

The correct approach should be to generalize the value . By abstracting a pattern, the very specific saveStudentA and saveStudentB are abstracted into saveStudentXXX.

XXX can be described as a kind of "generalization" or "generalization", which is equivalent to "so-and-so", referring to a certain student in general, but not to whom to avoid hard coding.

The so-called "pan" is the process from the specific to the general , or the process from the specific to the abstract .

A and B are special, while XXX is general;

Or that A and B are concrete, while XXX is abstract.

After generalization, that is, the value is " parameterized ", the saveStudent is finally obtained, as follows:

public void saveStudent(Student xxx) {
	// TODO pseudo code
	// insert xxx into table Student
}

This practice is so common that we don't feel like it's worth talking about.

generic type

In the previous step, we got saveStudent, is it over? How to say it, it sometimes depends on which angle you are talking about.

Suppose you and a colleague are developing a system, you are responsible for the "student module", you have a saveStudent method; your colleague is responsible for the "teacher module", he has a saveTeacher method.

public void saveTeacher(Teacher xxx) {
	// TODO pseudo code
	// insert xxx into table Teacher
}

For you as individuals, the problem of duplication has been resolved in the respective modules. But looking at it from a higher position, such as looking at the entire system from the perspective of an architect, then he will find that there are still duplications.

saveStudent and saveTeacher are still duplicates, aren't they? Even in the most intuitive sense, "save" is repeated.

image

If the specific implementation is also written, we will see more repetitions. For example, it may be necessary to obtain a database connection first, then submit after saving, and possibly exception handling code.

Is there any hardcoding? You might be thinking, hardcoding doesn't exist anymore. Whether it is a student or a teacher, it is passed in through parameters, as if nothing is written dead? But when we look closely at the parameter part of the method's signature, it is not difficult to find that the type is actually hard-coded:

image

For example, saveStudent can only accept the Student type;

And saveTeacher can only accept the Teacher type.

So can we go further and abstract? For example, generalizing the type as well? Can it also be parameterized as before? Before going further, let's take it easy and read a little story about Confucius.

Teaching without class

Confucius, as a great educator, once put forward the view that there is no distinction if there is education .

The so-called "there is no class", in simple words, it is probably "you are rich and handsome, no matter if you are a diaosi ( no class , don't care what the type of students is), I am willing to teach you (you teach ) ".

image

So why is he a great educator? Maybe it's because of his attitude that he doesn't limit himself to categories. For example, I only teach rich children or only poor students.

He's not picky about the type of people he teaches, which is clearly not the case with our two previous methods:

saveStudent only accepts Student type;

And saveTeacher only accepts the Teacher type.

Can it be possible to exist without class ?

generic method

Back to the topic of our programming, as a save action, it is best not to be limited to a particular category, but to be more general, so as to reduce the repetition caused by different types . We can write it like this (without considering how it is implemented):

public <XXX> void save (XXX xxx) {
	// TODO pseudo code
	// insert xxx into table XXX
}

Then, through the declaration of a type parameter (type parameter) such as <XXX> , the type in the parentheses is also generalized and parameterized, it is no longer "type hard-coded", and it also becomes variable.

Regarding XXX, it is usually written as T, which means Type. But this is just a variable name for a type parameter, so you can choose whatever you want, as long as there is no conflict.

Now, our value is alive, and the type is also alive, neither of which is hard-coded, nor does it say what type of value you must pass in. This method no longer has type constraints on incoming variables, either Student or Teacher class instances can be passed in. for example:

// ...
Student s = new Student();
Teacher t = new Teacher();
service.save(s);
service.save(t);

supertypes vs generics

One might think, can't I do this with an interface (or parent class) too? For example, if both Student and Teacher implement an interface called Entity (or inherit a parent class called Entity), then the save method uses Entity as the type:

public void save(Entity e) {
	// TODO pseudo code
	// insert e into table e.realType
}

Wouldn't it work as well? What is the meaning of generics?

generic return value

Now let's consider a more general case, for example, we also want to return a value, then through generics we can write like this, before returning void, now returning XXX type:

public <XXX> XXX save(XXX xxx) {
	// TODO pseudo code
	// insert xxx into table XXX
	return xxx;
}

As you can see, the return value can also be represented by XXX. This means that the type of the returned value is determined by the type of the value you pass in, which dynamically follows the type passed in. for example:

// ...
Student s = new Student();
Teacher t = new Teacher();
		
Student ss = service.save(s);
Teacher ts = service.save(t);

If you use the Entity interface, then you can only return the Entity interface, or you're missing the subtype:

// ...
// 丢失子类型
Entity ss = service.save(s);
Entity ts = service.save(t);

Either you have to do a cast:

// ...
// 强制类型转换
Student ss = (Student)service.save(s);
Teacher ts = (Teacher)service.save(t);

an example

Let's look at a simpler example, a method that compares which of the two values ​​is larger and returns the larger value (bigger, see who is higher~).

public static <C extends  Comparable<C>> C bigger(C c1, C c2) {
	return c1.compareTo(c2) > 0 ? c1 : c2;
}

As long as it implements the Comparable interface, the class can compare and return the larger value without type conversion.

Note: In order to simplify the code, cases such as the equality of two values ​​are not considered here.

Such as:

String biggerString = bigger("hello", "hi");
Integer biggerInteger = bigger(new Integer(8), new Integer(5));
System.out.println(biggerString);
System.out.println(biggerInteger);

It can be seen that the bigger method can receive both String and Integer, as long as both implement the Comparable interface, and the type of the return value changes with the type of the input value, without any coercion.

You can also define your own classes:

Bird longerBird = bigger(new Bird("18cm"), new Bird("12cm"));
Ball biggerBall = bigger(new Ball("A Cup"), new Ball("D Cup"));
System.out.println(longerBird);
System.out.println(biggerBall);

Just implement the interface:

public class Ball implements Comparable<Ball>{

	private String size;

	public Ball(String size) {
		this.size = size;
	}

	@Override
	public int compareTo(Ball ball) {
		return size.compareTo(ball.size);
	}
	
	@Override
	public String toString() {
		return size + " ball";
	}

}

public class Bird implements Comparable<Bird>{

	private String length;

	public Bird(String length) {
		this.length = length;
	}

	@Override
	public int compareTo(Bird bird) {
		return length.compareTo(bird.length);
	}
	
	@Override
	public String toString() {
		return length + " bird";
	}

}

template

Now, let's wrap up. A generic type , usually called a parameterized type , means that you can specify a type through a "parameter", but it's not the same as a normal parameter, it's a "type parameter", and then, all of a sudden You can simply define methods that apply to many different types, it's a type template .

In fact, there is no such thing as a "generic value". Since generics can be called "parameterized types", in reverse, "parameterized values" can naturally also be called "generic values". (It's normal if you think it's a bit far-fetched, is that really a "parameterized value"? In short, it's a bit difficult to think of an accurate and short name to summarize something, so I have to do it for now, later The same is true for the functionals of .)

What do you think of when it comes to templates? JSP? Freemarker? Velocity? Yep, it's very similar to these things. Take a page template like this as an example:

<span>Hello, ${name} ! </span>

Then when name="dog left", the result becomes:

<span>Hello, dog left ! </span>

And when name="yadan", the result becomes:

<span>Hello, jerk ! </span>

If you've done dynamic web development, this will be all too familiar to you. Now let's say our template looks like this:

public ${type} bigger(${type} t1, ${type} t2)

Then when type="String", you can get

public String bigger(String t1, String t2)

And when type="Integer", you can get

public Integer bigger(Integer t1, Integer t2)

Yes, generics are a template technique like this.

This is also known as meta-programming , which is code of code, abstraction of abstraction.

Imagine if you don't have generics and you want your method to fit multiple types, then you just keep overwriting your method:

public static String bigger(String b1, String b2) {
	return b1.compareTo(b2) > 0 ? b1 : b2;
}
	
public static Integer bigger(Integer b1, Integer b2) {
	return b1.compareTo(b2) > 0 ? b1 : b2;
}

Here you can see your method (specifically, the algorithm inside) repeating itself.

Literally the code is exactly the same!

Worse yet, it doesn't adapt to the user's constant new class definition, and for each new class the user defines, you may have to add another method.

// ...
public static Foo bigger(Foo b1, Foo b2) {
	return b1.compareTo(b2) > 0 ? b1 : b2;
}

Strong typing brings benefits, but sometimes it becomes a hindrance to cooperation, obviously very similar things, because the types lie there, you can only watch the code repeating and unable to integrate. The generic mechanism makes up for this shortcoming.

generic class

We talked about generics at the method level, but at the class level, you can also define generic type parameters.

An important role of the type is that the operations defined on it, such as the bigger above, even if it is a generic type, still inherits from the Comparable interface to get the compareTo method.

But sometimes, we don't care at all what operations can be performed on the type, sometimes it's just to "hold it" and then we might need to "get it back" later, as long as it's a "thing", we don't care at all What type it is, and we don't want the type to be a hindrance to our implementation.

As you may have thought, I said container classes . The following defines a simple generic container class:

public class MyList<XXX> {
	
	private XXX[] arr;
	private int i;
	
	// TODO constructor...

	public XXX get(int i) {
		return arr[i];
	}
	
	public void add(XXX xxx) {
		arr[i] = xxx;
		i++;
	}
}

Then when we declare MyList<String> strList = new MyList<>(); it is as if we have newly defined a class of ListString:

public class ListString {
	
	private String[] arr;
	private int i;
	
	// TODO constructor...

	public String get(int i) {
		return arr[i];
	}
	
	public void add(String xxx) {
		arr[i] = xxx;
		i++;
	}
}

The declaration process is like instantiating it with a specific type, and the add and get methods in it also become methods that can receive String parameters and return String parameters.

Naturally, when we declare MyList<Integer> intList = new MyList<>();, the situation is similar to that of the generic method, as if we defined another ListInteger class:

public class ListInteger {
	
	private Integer[] arr;
	private int i;
	
	// TODO constructor...

	public Integer get(int i) {
		return arr[i];
	}
	
	public void add(Integer xxx) {
		arr[i] = xxx;
		i++;
	}
}

For custom classes such as Foo, the principle is the same, and the code will not be listed here.

At this point, the generic class becomes our template, the template of the common class, another embodiment of metaprogramming, the code of code, the abstraction of abstraction!

If you pay attention to the methods in the container class, what do you find? These method names are very short, such as get, put, add, etc. Just like our save, it does not say that it is a method such as getString or getInteger. They are completely decoupled from the specific type and have nothing to do with the type. Yes Applicable to all types.

Type Safety Issues

In the early days, when there was no generic mechanism, to make the container class generic and implement the algorithm only once, we could only use Object, the ancestor of all classes, as the type of the method. This is almost always a type conversion when you take it out, and it's a bit of a risk, because the process of putting it in is also without type checking.

List list = new ArrayList();
		
// 放进去是 String
list.add("a string");
		
// 取出来就只能是 Object
Object obj = list.get(0);
		
// 或者需要强制类型转换
String s = (String) list.get(0);

This is where the so-called type safety problem comes from. But don't get me wrong, generics didn't exist to solve the so-called type safety problem inherently.

There is a very simple way to solve the security problem. For each type, we can simply repeat those similar codes, which can also bring security.

Just like the previous example, ListString, ListInteger, ListFoo... Every time you come to a type, I will define a corresponding container class.

But we certainly don't want to do that! The emergence of generics, the real driving force comes from the management of duplication, how to eliminate duplicate codes, how to make a set of algorithms and a set of codes more general, how to make codes reused , and at the same time Type safety is guaranteed.

It is even said that we first let the code solve the problem of type safety in a repeated way, and then we observe the repeated code and notice that there are only differences in types, we parameterize these differences, and finally the programming paradigm of generics emerges.

Summarize

Now, let's summarize, from generic values ​​to generics, the degree of abstraction is constantly improving, the difference is constantly externalized, parameterized, and finally there is only one verb, which has nothing to do with concrete values, and has nothing to do with concrete types.

image

The core here lies in pattern recognition, identifying the same and different parts, parameterizing the different parts, and retaining the same parts, which is the so-called extracted pattern.

image

image

In the future, we will distill larger and more abstract patterns. Due to space problems, the functional aspects will be left to the next part for further analysis.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324375974&siteId=291194637