0. 开始的地方
Handler(Selector sel, SocketChannel c) throws IOException {
...
sk.attach(this);
...
}
new Handler(selector, c);
Scalable IO in Java中看到这代码,总让我感觉有点骚东西。
前面的记录总简单的抒发了一下,现在让我天马行空的思维发散一回。
1. 对象引用问题
gc
回收什么垃圾?引用计数为0的对象就是垃圾了。到底什么是
引用
呢,怎么算是引用计数为0
呢。什么情况下,我们在代码里面再也找不到一个对象了呢。
1.1 变量名引用
public static void main(String[] args) {
Student student = new Student("godme");
Student godme = student;
student = new Student("judas");
student = godme;
}
仔细一看,哪个Student
被丢失了呢。
最后的结果就是,Studnet(godme)
被引用了两次,而Student(judas)
引用为0
。
这个时候,通过student
或者godme
,我都能找到Student(godme)
。
但是Student(judas)
在代码级别,我永远的失去了它的身影。
这种就是要被回收的垃圾,因为它失活
了。
1.2 容器引用
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>();
for(int id = 1; id < 5; id++){
students.add(new Student("student" + id));
}
}
在没有变量名引用的情况下,使用容器
也可以保持对象的活性
,防止被回收。
只要还有活性的对象,我们始终能找到它。
如果我们pop
之后没有引用继续指向实体,那么,它必然失活。
1.3 其他方式
public class Node {
private Student value;
private Node next;
private Node prev;
}
public static void main(String[] args) {
Node head = new Node(null);
Node cursor = head;
for(int id = 1; id < 5; id++){
cursor.setNext(new Node(new Student("student" + id)));
}
}
利用链表,或者其他的结构,保持引用,同样能够保证活性
。
这种方式同质于容器
引用,其中关键的地方,是活性
的传递性。
通过一个活性
的对象,引用到一个对象,这个被引用的对象必然也能够保持活性,也就是能被找到实体。
可以称之为伴生
,如果前面的对象失活
,伴生的对象们必定失活。
在对象管理的时候,依次用变量名保持活性,快捷但不方便,容易混乱,不易管理。
使用容器,容易管理,但是针对性不强。
如何引用一个对象,并保持其活性,这个就因地制宜了。
2.引用关系的建立
把基本对象排除,我们的任何操作中使用的元素都是对象。
程序的重点就是如何操作这些对象,因此,更好管理这些对象就能让我们更好的组织代码结构。
创建和回收先不论,关键是我们如何找到这些对象,也就是如何去引用,保证活性。
然后去找到实体,开展我们的具体操作。
2.1 被动伴生
public static void main(String[] args) {
Student student = new Student("godme");
ArrayList<Student> students = new ArrayList<>();
students.add(student);
}
这是我们最常见也最常用的管理模式。
不论是让实体关联变量名
Student student = new Student("godme");
ArrayList<Student> students = new ArrayList<>();
还是把对象添加到容器
students.add(student);
都是操作对象,去被引用
。
2.1.1 面向对象扩展
话说回来,我们使用面向对象
,但面向对象到底是什么呢?
归根结底,是被动
与主动
的差别。
# 面向过程
sayHello(student);
# 面向对象
student.sayHello();
面向过程中的函数,就好比是工具箱
,专门操作信息结构体
,信息都是被处理
。
面向对象中,属性
都是主动调用方法
进行自处理,是一种主动性的操作。
但是在我们面向对象编程
当中,却有太多的过程性被动
操作,主动性够不充足。
更多的主动性,是对于程序员而言的主动性,没有贯彻对象的主动性
。
2.2 主动伴生
class Peron{
public Person(Family family, name){
this.name = name;
family.add(this);
}
public static void main(String[] args){
Family family = new Family();
new Person(family, "godme")
}
}
贯彻一下面向对象的主动性
,当创建对象时就能够完成对象的自我收归管理了。
对于固定管理流程,我们没必要去外围的去管理他们,可以让他们自我进行管理。
类比于离职,现在都是员工自己收集资料,到处去盖章,然后完成流程。
如果整个流程,都需要外围的去管理,不同的员工,不同的部门,不同的印章,不同的流程,想想都头大。
但是如果让对象去主动,流程就是可复制,对于每个对象而言,更明确,也更便捷。
所以,自主缴费,自主申请之类的主动性操作,越来越多,也越来越容易处理。
2.3 循环引用
class Node{
Node parent;
Node child;
String value;
public void addChild(Node child){
this.child = child;
child.parent = this;
}
public void setParent(Node parent){
this.parent = parent;
parent.child = this;
}
}
用链表来说,再明确不过了。
在纯粹的对象管理里面,变量名是多余的废物操作。
在这种纯粹的关系管理,也就是引用传递,或者说伴生族群里面,对象单个依附总比容器挨个管理来的方便快捷。
同时,双向引用
这种模式里面,主动性更能够看出效率。
双向链表的互联操作,如果是外部进行关联,一个足够长的链表,就必须重复的进行关联操作。
但是收归个体主动操作,我们却只关心顺序,而不用考虑关系的建立过程。
不过,此小节的关注点,是对象的双向引用
,即两个对象的相互伴生
。
前面的伴生
,就像单向链表,只能够单方面的查找。
而双向链表中,可以你来我往
,找到其中一个,我们就能找到另一个。
class Person{
public void girlFriend(Person person){
this.girlFriend = person;
person.boyFriend = this;
}
public void boyFriend(Person person){
this.boyFriend = person;
person.girlFriend = this;
}
}
这是两个对象之间一种相互确立的伴生关系。
2.4 方法引用
如果是javascript
或者python
这种语言的话,方法
也是一种对象,就能够更好的讲述了。
就可以如此进行使用
handle.handler(handle)
只是,其中更深层的含义,是方法的可替换性
handle.handler = handler
在java
当中,方法是不可替换的,我们只能借由包含方法的对象替换,来完成方法的替换。
也就是利用接口
,来完成一种类似的函数式编程。
2.4.1 接口实现和接口属性
利用接口的话,一般存在两种形式:实现接口
和接口属性
。
研究两者的区别,可以让我们更好的利用接口,和进行接下来的讲述。
接口实现
class Person implements Hello{
public void hello(Person person){
doSomething;
}
}
可以说,有一点经验的都能够发现问题:实现接口后方法就固定了
。
尤其是对于一个类而言,只要是这个类的该方法,已经注定是一致的。
不仅不可替换,而且单一类的所有对象都将统一行为。
接口属性
class Person{
private Hello hello;
public void setHello(Hello hello){
this.hello = hello;
}
public void hello(){
this.hello.hello(this);
}
}
把接口作为属性,我们就可以自定义的替换该属性。
在替换过程中,我们就可以任意更换具体的操作方法,从而达到同样的方法不同的操作。
对于动作的更改,也就是方法的替换,存在三种方式。
- 子类重写
- 再次封装
- 接口属性替换
对于前面的两种而言,都会派生出新的类别,而且,新类别中的方法依然存在不可替换的性质。
如果想要同一动作的具体内容得到更改,尤其是任意对象都可更改,
接口属性
更改方案更实用。
3. 资源自处理
总结一下前面的内容
- 引用关系建立
- 主动性关系建立
- 主动方法对象关联
随后,引申出来的也是我们的终极目标:
资源的自处理
。
3.1 一次性处理
资源
public class Ticket {
private AtomicInteger total = new AtomicInteger(100);
private boolean ready = false;
public int count(){
return total.get();
}
public void decrease(){
this.total.decrementAndGet();
}
public void increase(){
this.total.incrementAndGet();
}
public boolean hasMore(){
return total.get() > 0;
}
public boolean isReady(){
return this.ready;
}
public void ready(){
this.ready = true;
}
}
处理器
public class Shop extends Thread{
private Ticket ticket;
public Shop(Ticket ticket){
this.ticket = ticket;
this.start();
}
private void sale(){
while(ticket.hasMore()){
ticket.decrease();
System.out.println(Thread.currentThread().getName() + " ticketCount : " + ticket.count());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while(!ticket.isReady()){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sale();
}
}
处理
public static void main(String[] args) {
Ticket ticket= new Ticket();
new Shop(ticket);
new Shop(tiket);
ticket.ready();
}
这一下,是不是更清楚所谓主动性
了呢。
如果商店本来就注定了卖票,我们只需要让商店自动进货就行了
new Shop(ticket);
贯彻面向对象,充分发挥了对象的主动性,只需要让商店自动拿票就行。
是在没必要知道哪一个商店,然后把票交给它。
3.2 多处理器处理
实际上,前面的例子就是存在多个消费者的例子了,为了省力气,也就一次性完工了。
想要单个例子的,去掉一个new Shop(ticket)
就行了。
这里想要说明两点
- 本来就注定关联的,主动关联可以更简单的管理对象和组织代码
- 多个处理器,做好安全措施,这种添加多个处理器进行护理更加快捷
3.3 顺序处理
3.3.1 异步顺序
一般业务来说,不同的处理逻辑之间,是有顺序关系的。
假设票买完了,需要重新印票。
public class Provider extends Thread {
private Ticket ticket;
public Provider(Ticket ticket){
this.ticket = ticket;
this.start();
}
private void publish(){
for(int count = 1; count <= 20; count++){
ticket.increase();
System.out.println(Thread.currentThread().getName() + " provider : " + ticket.count());
}
this.ticket.ready();
}
@Override
public void run() {
while(true){
while(ticket.isReady()){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
publish();
}
}
}
当然了,前面的Shop
消费完就需要ticket.disable
,因为有信号才能够进行控制。
public void disable(){
this.ready = false;
}
不过,这不是我想讲述的重点。
用线程来讲述的话将引入两个话题
- 动态
- 异步
在这个例子中,涉及到了动态
的资源处理,遮掩了我想讲述的主题。
线程一般处理耗操作,顺序控制的话一般会使用Future
或者其他对象进行异步控制,同样遮掩了基本的讲述。
这两个方面,算作是此篇内容的进阶,后续慢慢结合加深。
让我们更加深入理解关于引用
诞生的更多操纵手段。
3.3.1 同步顺序
3.3.1.1 方法替换
回顾前面的方法引用,也即是针对不同对象可更换不同操作,在讲述同步顺序之前,我们深入的体会一下此效果。
资源
public class Data {
public Executor executor;
public Integer value;
public Data(Integer value){
this.value = value;
}
public Integer calc() {
return executor.exec(this);
}
}
接口
public interface Executor {
Integer exec(Data data);
}
几个实现
public class PlusFive implements Executor {
@Override
public Integer exec(Data data) {
return data.value + 5;
}
}
public class DivTwo implements Executor {
@Override
public Integer exec(Data data) {
return data.value / 2;
}
}
public class MultTen implements Executor {
@Override
public Integer exec(Data data) {
return data.value * 10;
}
}
操作
public static void main(String[] args) {
Data data = new Data(3);
data.executor = new PlusFive();
System.out.println(data.calc());
data.executor = new DivTwo();
System.out.println(data.calc());
data.executor = new MultTen();
System.out.println(data.calc());
}
这里只是为了演示接口属性的可替换性
,后续将不再提及。
就此类操作而言,可以利用主动伴生
进行代码简化。
public abstract class ExecutorAdapter implements Executor{
public ExecutorAdapter(Data data){
data.executor = this;
}
}
把其他几个接口直接继承此适配器类。
public class PlusFive implements Executor;
public class DivTwo implements Executor;
public class MultTen implements Executor;
public static void main(String[] args) {
Data data = new Data(3);
new PlusFive(data);
System.out.println(data.calc());
new DivTwo(data);
System.out.println(data.calc());
new MultTen(data);
System.out.println(data.calc());
}
只是需要记得super
完成主动伴生。
经过如此操作,不难得出如此结论:
接口属性
直接伴生操作对象,不具有逻辑组合操作。这样的直接单层次伴生,能够直接操作对象,却不能够一次处理,最终组装成业务逻辑。
3.3.1.2 顺序管理
我们把原来的操作元素Data
中的executor
改成容器类。
public class Data {
public ArrayList<Executor> executors = new ArrayList<>();
public Integer value;
public Data(Integer value){
this.value = value;
}
public void calc() {
Iterator<Executor> it = executors.iterator();
while(it.hasNext()){
it.next().exec(this);
}
}
}
这样,就完成了逻辑顺序的组装。
伴生的时候,伴生于executors
,自动创建关联关系。
public abstract class ExecutorAdapter implements Executor{
public ExecutorAdapter(Data data){
data.executors.add(this);
}
}
还需要注意一点,也是最终要的点,执行结果必须写回
Data
,因为它始终贯穿整个流程。data.value /= 2; data.value *= 10; data.value += 5;
操作
public static void main(String[] args) {
Data data = new Data(3);
new PlusFive(data);
new DivTwo(data);
new MultTen(data);
data.calc();
System.out.println(data.value);
}
Context
到这里,不知道大家发现了最核心的转变了没有,那就是上下文
Context
。从
new Shop(ticket)
类型的伴生,让我们发现了伴生,也就是引用传递
的效果。增加伴生的层级,我们完全可以
一只根须扯出一座山
。然后将这只根须,伴生在一个对象上面,就能够让这个对象贯穿整个的流程。
也就是所谓的
context
。上面的例子,存在几个缺点
- 直接伴生数据,数据未分离
- 单链表,有下文没上文
如果我们采用了双向链表,上下文就可以真正的实现。
分离了数据,就可以做到
context
的流程完备性,完全脱离数据依赖,单纯的流程定制。而且,在一般的业务处理当中,逻辑都是固定的,只需要这么一个
规则树
,就可以处理更多的数据。
3.3.1.3 上下文管理
Context
public class Context {
public ArrayList<Executor> executors = new ArrayList<>();
public ArrayList<Data> datas = new ArrayList<>();
public void deal(){
Iterator<Data> dataIt = datas.iterator();
Iterator<Executor> executorIt;
Data data;
while (dataIt.hasNext()){
data = dataIt.next();
executorIt = executors.iterator();
while (executorIt.hasNext()){
executorIt.next().exec(data);
}
}
}
}
Data
public class Data {
public Integer value;
public Data(Context context, Integer value){
this.value = value;
context.datas.add(this);
}
@Override
public String toString() {
return String.valueOf(value);
}
}
main
public class OrderMain {
public static void main(String[] args) {
Context context = new Context();
new PlusFive(context);
new DivTwo(context);
new MultTen(context);
new Data(context, 3);
new Data(context, 5);
context.deal();
System.out.println(context.datas.toString());
}
}
动作的绑定切换到context
。
这个实现中,有几点问题
- 有下文无上文
- 上下文和规则树形象重叠
具体怎么做就自己发挥了,暂时就这样了。
说过的循环绑定,之所以没有用上,这个问题值得思考。
因为,上下文贯穿了整个流程,所有的修改都必须写回上下文中或伴生的对象当中去。
这样才能够经历全部的流程操作。
如果方法对象直接绑定了操作元素,必须保证如下几点
- 严格控制方法执行顺序
- 做严格数据检查
- 结果回写对象
防止对象更换和结果丢失。
但是弊端更明显,那就是造成方法对象多实例,并且丢失动态数据功能。
如果把一切都放在
context
中,生命周期更长,可以随时往context
中增加或修改新的变量。更利于调整后续操作分支和控制。
3.4 分支跳转
前面的context
还存在一个弱点,那就是分支跳转,如果只能够单向执行,也就没什么意思了。
容器节点
public abstract class Node implements NodeExecutor {
public HashMap<Event,Node> nodes = new HashMap<>();
public Node nextNode(Event event){
if (nodes.size() <= 0){
return null;
}
if(nodes.size() == 1){
return nodes.values().iterator().next();
}
if(nodes.containsKey(event)){
return nodes.get(event);
}
return null;
}
public void addNode(Event event, Node node){
nodes.put(event, node);
}
public boolean hasNext(){
return this.nodes.size() > 0;
}
}
处理器
public interface NodeExecutor {
Event exec(Data data);
}
NodeContext
public class NodeContext {
private Node tree;
public NodeContext(Node node){
this.tree = node;
}
public ArrayList<Data> datas = new ArrayList<>();
public void addData(){
}
public void deal(){
Iterator<Data> dataIt = datas.iterator();
Data data;
while(dataIt.hasNext()){
data = dataIt.next();
Node cursor = tree;
while(cursor != null){
Event event = cursor.exec(data);
cursor = cursor.nextNode(event);
}
}
}
}
Event
public enum Event {
MORETHREE,
LESSTHREE,
MINUS,
PLUS,
MORETEN,
LESSTEN;
}
最终实现如下的固定结构
组合逻辑如此
main
public static void main(String[] args) {
Node printNode = new PrintNode();
Node divThreeNode = new DivThreeNode();
Node plusTwoNode = new PlusTwoNode();
Node mulEightNode = new MulEightNode();
divThreeNode.addNode(null, printNode);
plusTwoNode.addNode(null, printNode);
mulEightNode.addNode(null, printNode);
Node moreTenNode = new MoreTenNode();
moreTenNode.addNode(Event.MORETEN, divThreeNode);
moreTenNode.addNode(Event.LESSTEN, plusTwoNode);
Node moreThreeNode = new MoreThreeNode();
moreThreeNode.addNode(Event.MORETHREE, moreTenNode);
moreThreeNode.addNode(Event.LESSTHREE, mulEightNode);
Node absNode = new AbsNode();
Node transnode = new TransNode();
absNode.addNode(Event.MINUS, transnode);
absNode.addNode(Event.PLUS, moreThreeNode);
///////////////////////////////////////////
NodeContext context = new NodeContext(absNode);
new Data(context, 4);
context.deal();
}
当利用分支节点组装好逻辑业务,后续的业务处理就只是单纯的数据添加。
不用再费心费力的区各种判断,而且业务一旦变更,在对应的节点上面进行局部的修改,整体逻辑不变。
费力只在业务逻辑的节点关系组装,后续的维护和再开发,都十分的简单。
4. 小结
- 简单引用
- 主动引用
- 方法引用
- 层级引用
- 分支跳转
从简单的一级引用开始发散,引申到一个伴生群
。
把握单一入口,就可以构建一个贯穿始终的对象,也即是上下文Context
。
通过层级的引用关系,搭建方法的流程操作逻辑,就可以组装成业务树,宏观上对业务进行分支描述。
让开发更加易于维护和扩展。
这种业务规则的描述,如今普遍的drools
等规则引擎,做的不外如此。
通过这次的放飞思想,我有如下收获
- 隐晦引用(主动伴生)
- 整体逻辑组合描述(方法引用逻辑)
- 上下文
Context
的贯穿方法(贯穿业务树)
当然,这些东西不用这种方式也可以进行编写,但是对应的有如下缺点
- 重复引用
- 业务不清晰
Context
零散注入
尤其你可以看见最后的代码,正是因为Node
没有使用伴生
的方式,一级一级的注入,太多无用引用。
但是也可以明确的去抽象还原业务逻辑。
抛开业务组装,后续的操作,简直不要太简洁。
而且,在这种模式下的业务改动,仍然可以快速的锁定修改点,快速的添加节点补充业务。
只能说,世上无小事,什么都能玩出花样。
简单的Person a = new Person()
,背后也蕴含了无限的可能。
垃圾代码在此。