**Description**

Currently `BaseOperator` and `XComArgs` implements the same logic of `__lshift__`, `__rshift__` and others "chain" operations (`>>`, `<<`). It seems resonable to abstract this logic into `TaskMixin` to introduce some DRYness especially because the new concept of `TaskGroup` will also implement the same methods.

**Use case / motivation**

Limit duplication of logic.

**Related Issues**

https://github.com/apache/airflow/pull/10827#issuecomment-691927501
#10153 

airflow/models/baseoperator.py
"The weight_rule must be one of {all_weight_rules},"
                "'{d}.{t}'; received '{tr}'."
                .format(all_weight_rules=WeightRule.all_weight_rules,
                        d=dag.dag_id if dag else "", t=task_id, tr=weight_rule))
        self.weight_rule = weight_rule
        self.resources: Optional[Resources] = Resources(**resources) if resources else None
        self.run_as_user = run_as_user
        self.task_concurrency = task_concurrency
        self.executor_config = executor_config or {}
        self.do_xcom_push = do_xcom_push
        
        self._upstream_task_ids: Set[str] = set()
        self._downstream_task_ids: Set[str] = set()
        self._dag = None
        self.dag = dag or DagContext.get_current_dag()
        
        
        from airflow.models.dag import DAG
        self.subdag: Optional[DAG] = None
        self._log = logging.getLogger("airflow.task.operators")
        
        self.inlets: List = []
        self.outlets: List = []
        self._inlets: List = []
        self._outlets: List = []
        if inlets:
            self._inlets = inlets if isinstance(inlets, list) else [inlets, ]
        if outlets:
            self._outlets = outlets if isinstance(outlets, list) else [outlets, ]
    def __eq__(self, other):